@asiainfo-sdd/multica-cli 0.3.8 → 0.3.10

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.
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- // multica-uninstall 命令入口:委托 lib/uninstall.js。
5
- require('../lib/uninstall.js');
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // multica-uninstall 命令入口:委托 lib/uninstall.js。
5
+ require('../lib/uninstall.js');
package/bin/multica.js CHANGED
@@ -1,49 +1,49 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- // bin wrapper:把参数原样转给 vendor/ 下的平台二进制。
5
- // 对外唯一命令名是 multica(aido 入口已移除)。
6
- // 二进制的子命令(daemon/login/config/...)与参数原样透传。
7
-
8
- const { spawn } = require('child_process');
9
- const fs = require('fs');
10
- const path = require('path');
11
- const { detectPlatform, vendorBinaryPath } = require('../lib/platform');
12
- const pkg = require('../package.json');
13
-
14
- const plat = detectPlatform();
15
- const binary =
16
- plat && vendorBinaryPath(path.join(__dirname, '..', 'vendor'), plat);
17
-
18
- if (!binary || !fs.existsSync(binary)) {
19
- console.error('✗ multica 二进制尚未下载。');
20
- console.error(` 修复方式:npm rebuild ${pkg.name}`);
21
- console.error(' (或设置 MULTICA_SKIP_BINARY_INSTALL=1 显式跳过后自备二进制)');
22
- process.exit(127);
23
- }
24
-
25
- const child = spawn(binary, process.argv.slice(2), {
26
- stdio: 'inherit',
27
- env: process.env,
28
- });
29
-
30
- // Windows 上 Ctrl-C 走 SIGINT/Break;转发常见终止信号。
31
- ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK'].forEach((sig) => {
32
- process.on(sig, () => child.kill(sig));
33
- });
34
- // Windows spawn .exe 时 kill('SIGTERM') 可能不生效,兜底强杀。
35
- process.on('exit', () => {
36
- if (!child.killed) child.kill();
37
- });
38
-
39
- child.on('error', (err) => {
40
- console.error(`✗ 无法执行 multica 二进制:${err.message}`);
41
- process.exit(126);
42
- });
43
-
44
- child.on('close', (code, signal) => {
45
- if (signal) {
46
- process.exit(128);
47
- }
48
- process.exit(code ?? 1);
49
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // bin wrapper:把参数原样转给 vendor/ 下的平台二进制。
5
+ // 对外唯一命令名是 multica(aido 入口已移除)。
6
+ // 二进制的子命令(daemon/login/config/...)与参数原样透传。
7
+
8
+ const { spawn } = require('child_process');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { detectPlatform, vendorBinaryPath } = require('../lib/platform');
12
+ const pkg = require('../package.json');
13
+
14
+ const plat = detectPlatform();
15
+ const binary =
16
+ plat && vendorBinaryPath(path.join(__dirname, '..', 'vendor'), plat);
17
+
18
+ if (!binary || !fs.existsSync(binary)) {
19
+ console.error('✗ multica 二进制尚未下载。');
20
+ console.error(` 修复方式:npm rebuild ${pkg.name}`);
21
+ console.error(' (或设置 MULTICA_SKIP_BINARY_INSTALL=1 显式跳过后自备二进制)');
22
+ process.exit(127);
23
+ }
24
+
25
+ const child = spawn(binary, process.argv.slice(2), {
26
+ stdio: 'inherit',
27
+ env: process.env,
28
+ });
29
+
30
+ // Windows 上 Ctrl-C 走 SIGINT/Break;转发常见终止信号。
31
+ ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK'].forEach((sig) => {
32
+ process.on(sig, () => child.kill(sig));
33
+ });
34
+ // Windows spawn .exe 时 kill('SIGTERM') 可能不生效,兜底强杀。
35
+ process.on('exit', () => {
36
+ if (!child.killed) child.kill();
37
+ });
38
+
39
+ child.on('error', (err) => {
40
+ console.error(`✗ 无法执行 multica 二进制:${err.message}`);
41
+ process.exit(126);
42
+ });
43
+
44
+ child.on('close', (code, signal) => {
45
+ if (signal) {
46
+ process.exit(128);
47
+ }
48
+ process.exit(code ?? 1);
49
+ });
package/lib/uninstall.js CHANGED
@@ -1,263 +1,263 @@
1
- 'use strict';
2
-
3
- // 卸载 multica(daemon 客户端)及其全部安装产物,一条命令覆盖:
4
- // 停 daemon → 删 ~/.multica 配置 → 删独立二进制 → 删命令入口(shim) → 卸 npm 全局包
5
- // 入口:bin/multica-uninstall.js(命令 multica-uninstall)。
6
- //
7
- // 为什么需要独立命令:npm v7 起不再执行 preuninstall 等卸载生命周期脚本,
8
- // `npm un -g` 无法触发包内清理;而本包"只管安装",卸载由用户显式执行一次即可。
9
- //
10
- // 现实背景:同一台机器上 multica 命令可能来自多处——npm 全局包(test/prod 两个
11
- // profile 名)、独立安装的 ~/.local/bin/multica[.exe]、以及历史 npm prefix 残留的
12
- // shim。逐一处理,避免"卸了还残留、卸了还能用"。
13
-
14
- const fs = require('fs');
15
- const os = require('os');
16
- const path = require('path');
17
- const { spawnSync } = require('child_process');
18
-
19
- // 同机可能安装的两个 profile 包名(test/prod 互斥,历史可能都残留)。
20
- const CANDIDATE_PKGS = [
21
- '@asiainfo-sdd/multica-cli',
22
- '@asiainfo-sdd/multica-cli-test',
23
- ];
24
-
25
- const isWin = process.platform === 'win32';
26
-
27
- const log = (msg) => process.stdout.write(`${msg}\n`);
28
- const ok = (msg) => process.stdout.write(`✓ ${msg}\n`);
29
- const warn = (msg) => process.stdout.write(`⚠ ${msg}\n`);
30
-
31
- // Windows 上 npm 是 .cmd,必须经 cmd.exe 执行;直接 spawn 并用 shell:true 会触发
32
- // Node DEP0190(args 未转义)。统一走 cmd /c 规避。
33
- function runCmd(cmd, args) {
34
- if (isWin) {
35
- return spawnSync(process.env.ComSpec || 'cmd.exe', ['/c', cmd, ...args], {
36
- encoding: 'utf8',
37
- });
38
- }
39
- return spawnSync(cmd, args, { encoding: 'utf8' });
40
- }
41
-
42
- function npmRun(args) {
43
- return runCmd('npm', args);
44
- }
45
-
46
- function configDir() {
47
- return path.join(os.homedir(), '.multica');
48
- }
49
-
50
- // 独立安装(非 npm)的二进制:~/.local/bin/multica[.exe]。
51
- function standaloneBinary() {
52
- return path.join(os.homedir(), '.local', 'bin', `multica${isWin ? '.exe' : ''}`);
53
- }
54
-
55
- // 当前 PATH 上所有 multica 命令入口(可能多个)。
56
- function findOnPath() {
57
- const r = isWin
58
- ? spawnSync('where', ['multica'], { encoding: 'utf8' })
59
- : spawnSync('which', ['-a', 'multica'], { encoding: 'utf8' });
60
- if (r.status !== 0 || !r.stdout) return [];
61
- return r.stdout
62
- .split(/\r?\n/)
63
- .map((s) => s.trim())
64
- .filter(Boolean);
65
- }
66
-
67
- // multica 命令可能来自多个 npm 全局目录(nvm prefix、%APPDATA%\npm、历史 prefix),
68
- // 而 `npm root -g` 只报当前一个 —— 逐一收集,避免漏删"卸了还能用"。
69
- function candidatePrefixes() {
70
- const set = new Set();
71
- const rootG = npmRun(['root', '-g']);
72
- if (rootG.status === 0 && rootG.stdout) {
73
- set.add(path.dirname(rootG.stdout.trim())); // …/node_modules → 前缀
74
- }
75
- const prefix = npmRun(['config', 'get', 'prefix']);
76
- if (prefix.status === 0 && prefix.stdout) {
77
- const p = prefix.stdout.trim();
78
- if (p && p !== 'undefined') set.add(p);
79
- }
80
- if (isWin && process.env.APPDATA) set.add(path.join(process.env.APPDATA, 'npm'));
81
- for (const bin of findOnPath()) set.add(path.dirname(bin)); // shim 所在目录即 prefix
82
- return [...set].filter(Boolean);
83
- }
84
-
85
- function installedNpmPkgs() {
86
- const found = []; // {prefix, name}
87
- for (const prefix of candidatePrefixes()) {
88
- for (const name of CANDIDATE_PKGS) {
89
- if (fs.existsSync(path.join(prefix, 'node_modules', name))) {
90
- found.push({ prefix, name });
91
- }
92
- }
93
- }
94
- return found;
95
- }
96
-
97
- function uninstallNpmPkgs(found) {
98
- const failed = [];
99
- for (const { prefix, name } of found) {
100
- const r = npmRun(['uninstall', '-g', '--prefix', prefix, name]);
101
- const dir = path.join(prefix, 'node_modules', name);
102
- if (r.status === 0 && !fs.existsSync(dir)) {
103
- ok(`已卸载:${name}(prefix ${prefix})`);
104
- continue;
105
- }
106
- // npm uninstall 失败或残留:直接删包目录兜底。
107
- try {
108
- fs.rmSync(dir, { recursive: true, force: true });
109
- fs.rmSync(path.join(prefix, 'node_modules', '@asiainfo-sdd'),
110
- { recursive: true, force: true });
111
- ok(`已删除包目录:${dir}`);
112
- } catch (err) {
113
- failed.push(`${name}(prefix ${prefix})`);
114
- warn(`删除失败:${dir} — ${err.message}`);
115
- }
116
- }
117
- return failed;
118
- }
119
-
120
- // ---------- 各清理步骤 ----------
121
-
122
- function stopDaemon() {
123
- const onPath = findOnPath();
124
- if (!onPath.length) {
125
- log('· 未发现 multica 命令,跳过 daemon 停止');
126
- return;
127
- }
128
- const r = runCmd('multica', ['daemon', 'stop']);
129
- const text = `${r.stdout || ''}${r.stderr || ''}`.trim();
130
- if (r.status === 0) ok('daemon 已停止(原本未运行则无操作)');
131
- else warn(`daemon stop 未成功(可忽略,继续卸载):${text || `exit ${r.status}`}`);
132
- }
133
-
134
- function removeConfig(keepConfig) {
135
- const dir = configDir();
136
- if (!fs.existsSync(dir)) {
137
- log(`· 配置目录不存在:${dir}`);
138
- return;
139
- }
140
- if (keepConfig) {
141
- log(`· 保留配置目录(--keep-config):${dir}`);
142
- return;
143
- }
144
- fs.rmSync(dir, { recursive: true, force: true });
145
- ok(`已删除配置目录:${dir}`);
146
- }
147
-
148
- function removeStandaloneBinary() {
149
- const bin = standaloneBinary();
150
- if (!fs.existsSync(bin)) {
151
- log(`· 无独立安装的二进制:${bin}`);
152
- return;
153
- }
154
- try {
155
- fs.rmSync(bin, { force: true });
156
- ok(`已删除独立二进制:${bin}`);
157
- } catch (err) {
158
- warn(`删除失败(可能被占用):${bin} — ${err.message}`);
159
- }
160
- }
161
-
162
- // 删除 PATH 上的命令入口(npm shim:.cmd/.ps1/无扩展名)。
163
- // 独立 exe 由 removeStandaloneBinary 处理,这里跳过 .exe。
164
- function removeShims() {
165
- const bin = standaloneBinary();
166
- let removed = 0;
167
- for (const p of findOnPath()) {
168
- if (path.resolve(p) === path.resolve(bin)) continue;
169
- const base = path.basename(p).toLowerCase();
170
- if (!/^multica(\.cmd|\.ps1|\.bat)?$/.test(base)) continue; // 只处理 shim,跳过 .exe/其他
171
- try {
172
- fs.rmSync(p, { force: true });
173
- ok(`已删除命令入口:${p}`);
174
- removed++;
175
- } catch (err) {
176
- warn(`删除失败:${p} — ${err.message}`);
177
- }
178
- }
179
- if (!removed) log('· 无残留命令入口(shim)');
180
- }
181
-
182
- // ---------- 入口 ----------
183
-
184
- function parseArgs(argv) {
185
- const opts = { yes: false, keepConfig: false, dryRun: false };
186
- for (const a of argv) {
187
- if (a === '-y' || a === '--yes') opts.yes = true;
188
- else if (a === '--keep-config') opts.keepConfig = true;
189
- else if (a === '--dry-run') opts.dryRun = true;
190
- else if (a === '-h' || a === '--help') opts.help = true;
191
- }
192
- return opts;
193
- }
194
-
195
- function printHelp() {
196
- log('用法:multica-uninstall [选项]');
197
- log('');
198
- log('彻底卸载 multica(daemon 客户端):停 daemon、删 ~/.multica 配置、');
199
- log('删独立二进制与命令入口、卸载 npm 全局包。');
200
- log('');
201
- log('选项:');
202
- log(' -y, --yes 跳过交互确认(脚本/非交互环境必须显式指定)');
203
- log(' --keep-config 保留 ~/.multica 配置目录(默认删除)');
204
- log(' --dry-run 只打印将要执行的操作,不实际修改');
205
- log(' -h, --help 显示本帮助');
206
- }
207
-
208
- function confirm() {
209
- if (!process.stdin.isTTY) {
210
- warn('非交互环境:需显式加 --yes 才会执行卸载。');
211
- return false;
212
- }
213
- process.stdout.write('确认卸载 multica 及其全部本地数据(含 ~/.multica)?[y/N] ');
214
- const buf = Buffer.alloc(1);
215
- try {
216
- const n = fs.readSync(0, buf, 0, 1, null);
217
- return n > 0 && /^y/i.test(buf.toString());
218
- } catch {
219
- return false;
220
- }
221
- }
222
-
223
- function main() {
224
- const opts = parseArgs(process.argv.slice(2));
225
- if (opts.help) return printHelp();
226
-
227
- log('即将卸载 multica(daemon 客户端)');
228
- log('检测到的 multica 命令入口:');
229
- const onPath = findOnPath();
230
- if (onPath.length) onPath.forEach((p) => log(` · ${p}`));
231
- else log(' · (PATH 中未发现 multica)');
232
- log('');
233
-
234
- if (opts.dryRun) {
235
- log('(--dry-run)将执行:停 daemon → 删配置 → 删独立二进制 → 删命令入口 → 卸 npm 包');
236
- log(` 配置目录:${configDir()}`);
237
- log(` 独立二进制:${standaloneBinary()}`);
238
- const pkgs = installedNpmPkgs();
239
- log(` npm 全局包:${pkgs.length ? pkgs.map((p) => `${p.name}(${p.prefix})`).join('; ') : '(无)'}`);
240
- return;
241
- }
242
-
243
- if (!confirm()) {
244
- log('已取消。');
245
- return;
246
- }
247
-
248
- stopDaemon();
249
- removeConfig(opts.keepConfig);
250
- removeStandaloneBinary();
251
- removeShims();
252
- const failed = uninstallNpmPkgs(installedNpmPkgs());
253
-
254
- log('');
255
- if (failed.length) {
256
- warn(`以下 npm 包未能卸载(多为当前进程占用文件,重开终端后手动执行即可):`);
257
- failed.forEach((n) => log(` npm uninstall -g ${n}`));
258
- } else {
259
- ok('multica 已卸载完成。重开终端以确保 PATH 刷新。');
260
- }
261
- }
262
-
263
- main();
1
+ 'use strict';
2
+
3
+ // 卸载 multica(daemon 客户端)及其全部安装产物,一条命令覆盖:
4
+ // 停 daemon → 删 ~/.multica 配置 → 删独立二进制 → 删命令入口(shim) → 卸 npm 全局包
5
+ // 入口:bin/multica-uninstall.js(命令 multica-uninstall)。
6
+ //
7
+ // 为什么需要独立命令:npm v7 起不再执行 preuninstall 等卸载生命周期脚本,
8
+ // `npm un -g` 无法触发包内清理;而本包"只管安装",卸载由用户显式执行一次即可。
9
+ //
10
+ // 现实背景:同一台机器上 multica 命令可能来自多处——npm 全局包(test/prod 两个
11
+ // profile 名)、独立安装的 ~/.local/bin/multica[.exe]、以及历史 npm prefix 残留的
12
+ // shim。逐一处理,避免"卸了还残留、卸了还能用"。
13
+
14
+ const fs = require('fs');
15
+ const os = require('os');
16
+ const path = require('path');
17
+ const { spawnSync } = require('child_process');
18
+
19
+ // 同机可能安装的两个 profile 包名(test/prod 互斥,历史可能都残留)。
20
+ const CANDIDATE_PKGS = [
21
+ '@asiainfo-sdd/multica-cli',
22
+ '@asiainfo-sdd/multica-cli-test',
23
+ ];
24
+
25
+ const isWin = process.platform === 'win32';
26
+
27
+ const log = (msg) => process.stdout.write(`${msg}\n`);
28
+ const ok = (msg) => process.stdout.write(`✓ ${msg}\n`);
29
+ const warn = (msg) => process.stdout.write(`⚠ ${msg}\n`);
30
+
31
+ // Windows 上 npm 是 .cmd,必须经 cmd.exe 执行;直接 spawn 并用 shell:true 会触发
32
+ // Node DEP0190(args 未转义)。统一走 cmd /c 规避。
33
+ function runCmd(cmd, args) {
34
+ if (isWin) {
35
+ return spawnSync(process.env.ComSpec || 'cmd.exe', ['/c', cmd, ...args], {
36
+ encoding: 'utf8',
37
+ });
38
+ }
39
+ return spawnSync(cmd, args, { encoding: 'utf8' });
40
+ }
41
+
42
+ function npmRun(args) {
43
+ return runCmd('npm', args);
44
+ }
45
+
46
+ function configDir() {
47
+ return path.join(os.homedir(), '.multica');
48
+ }
49
+
50
+ // 独立安装(非 npm)的二进制:~/.local/bin/multica[.exe]。
51
+ function standaloneBinary() {
52
+ return path.join(os.homedir(), '.local', 'bin', `multica${isWin ? '.exe' : ''}`);
53
+ }
54
+
55
+ // 当前 PATH 上所有 multica 命令入口(可能多个)。
56
+ function findOnPath() {
57
+ const r = isWin
58
+ ? spawnSync('where', ['multica'], { encoding: 'utf8' })
59
+ : spawnSync('which', ['-a', 'multica'], { encoding: 'utf8' });
60
+ if (r.status !== 0 || !r.stdout) return [];
61
+ return r.stdout
62
+ .split(/\r?\n/)
63
+ .map((s) => s.trim())
64
+ .filter(Boolean);
65
+ }
66
+
67
+ // multica 命令可能来自多个 npm 全局目录(nvm prefix、%APPDATA%\npm、历史 prefix),
68
+ // 而 `npm root -g` 只报当前一个 —— 逐一收集,避免漏删"卸了还能用"。
69
+ function candidatePrefixes() {
70
+ const set = new Set();
71
+ const rootG = npmRun(['root', '-g']);
72
+ if (rootG.status === 0 && rootG.stdout) {
73
+ set.add(path.dirname(rootG.stdout.trim())); // …/node_modules → 前缀
74
+ }
75
+ const prefix = npmRun(['config', 'get', 'prefix']);
76
+ if (prefix.status === 0 && prefix.stdout) {
77
+ const p = prefix.stdout.trim();
78
+ if (p && p !== 'undefined') set.add(p);
79
+ }
80
+ if (isWin && process.env.APPDATA) set.add(path.join(process.env.APPDATA, 'npm'));
81
+ for (const bin of findOnPath()) set.add(path.dirname(bin)); // shim 所在目录即 prefix
82
+ return [...set].filter(Boolean);
83
+ }
84
+
85
+ function installedNpmPkgs() {
86
+ const found = []; // {prefix, name}
87
+ for (const prefix of candidatePrefixes()) {
88
+ for (const name of CANDIDATE_PKGS) {
89
+ if (fs.existsSync(path.join(prefix, 'node_modules', name))) {
90
+ found.push({ prefix, name });
91
+ }
92
+ }
93
+ }
94
+ return found;
95
+ }
96
+
97
+ function uninstallNpmPkgs(found) {
98
+ const failed = [];
99
+ for (const { prefix, name } of found) {
100
+ const r = npmRun(['uninstall', '-g', '--prefix', prefix, name]);
101
+ const dir = path.join(prefix, 'node_modules', name);
102
+ if (r.status === 0 && !fs.existsSync(dir)) {
103
+ ok(`已卸载:${name}(prefix ${prefix})`);
104
+ continue;
105
+ }
106
+ // npm uninstall 失败或残留:直接删包目录兜底。
107
+ try {
108
+ fs.rmSync(dir, { recursive: true, force: true });
109
+ fs.rmSync(path.join(prefix, 'node_modules', '@asiainfo-sdd'),
110
+ { recursive: true, force: true });
111
+ ok(`已删除包目录:${dir}`);
112
+ } catch (err) {
113
+ failed.push(`${name}(prefix ${prefix})`);
114
+ warn(`删除失败:${dir} — ${err.message}`);
115
+ }
116
+ }
117
+ return failed;
118
+ }
119
+
120
+ // ---------- 各清理步骤 ----------
121
+
122
+ function stopDaemon() {
123
+ const onPath = findOnPath();
124
+ if (!onPath.length) {
125
+ log('· 未发现 multica 命令,跳过 daemon 停止');
126
+ return;
127
+ }
128
+ const r = runCmd('multica', ['daemon', 'stop']);
129
+ const text = `${r.stdout || ''}${r.stderr || ''}`.trim();
130
+ if (r.status === 0) ok('daemon 已停止(原本未运行则无操作)');
131
+ else warn(`daemon stop 未成功(可忽略,继续卸载):${text || `exit ${r.status}`}`);
132
+ }
133
+
134
+ function removeConfig(keepConfig) {
135
+ const dir = configDir();
136
+ if (!fs.existsSync(dir)) {
137
+ log(`· 配置目录不存在:${dir}`);
138
+ return;
139
+ }
140
+ if (keepConfig) {
141
+ log(`· 保留配置目录(--keep-config):${dir}`);
142
+ return;
143
+ }
144
+ fs.rmSync(dir, { recursive: true, force: true });
145
+ ok(`已删除配置目录:${dir}`);
146
+ }
147
+
148
+ function removeStandaloneBinary() {
149
+ const bin = standaloneBinary();
150
+ if (!fs.existsSync(bin)) {
151
+ log(`· 无独立安装的二进制:${bin}`);
152
+ return;
153
+ }
154
+ try {
155
+ fs.rmSync(bin, { force: true });
156
+ ok(`已删除独立二进制:${bin}`);
157
+ } catch (err) {
158
+ warn(`删除失败(可能被占用):${bin} — ${err.message}`);
159
+ }
160
+ }
161
+
162
+ // 删除 PATH 上的命令入口(npm shim:.cmd/.ps1/无扩展名)。
163
+ // 独立 exe 由 removeStandaloneBinary 处理,这里跳过 .exe。
164
+ function removeShims() {
165
+ const bin = standaloneBinary();
166
+ let removed = 0;
167
+ for (const p of findOnPath()) {
168
+ if (path.resolve(p) === path.resolve(bin)) continue;
169
+ const base = path.basename(p).toLowerCase();
170
+ if (!/^multica(\.cmd|\.ps1|\.bat)?$/.test(base)) continue; // 只处理 shim,跳过 .exe/其他
171
+ try {
172
+ fs.rmSync(p, { force: true });
173
+ ok(`已删除命令入口:${p}`);
174
+ removed++;
175
+ } catch (err) {
176
+ warn(`删除失败:${p} — ${err.message}`);
177
+ }
178
+ }
179
+ if (!removed) log('· 无残留命令入口(shim)');
180
+ }
181
+
182
+ // ---------- 入口 ----------
183
+
184
+ function parseArgs(argv) {
185
+ const opts = { yes: false, keepConfig: false, dryRun: false };
186
+ for (const a of argv) {
187
+ if (a === '-y' || a === '--yes') opts.yes = true;
188
+ else if (a === '--keep-config') opts.keepConfig = true;
189
+ else if (a === '--dry-run') opts.dryRun = true;
190
+ else if (a === '-h' || a === '--help') opts.help = true;
191
+ }
192
+ return opts;
193
+ }
194
+
195
+ function printHelp() {
196
+ log('用法:multica-uninstall [选项]');
197
+ log('');
198
+ log('彻底卸载 multica(daemon 客户端):停 daemon、删 ~/.multica 配置、');
199
+ log('删独立二进制与命令入口、卸载 npm 全局包。');
200
+ log('');
201
+ log('选项:');
202
+ log(' -y, --yes 跳过交互确认(脚本/非交互环境必须显式指定)');
203
+ log(' --keep-config 保留 ~/.multica 配置目录(默认删除)');
204
+ log(' --dry-run 只打印将要执行的操作,不实际修改');
205
+ log(' -h, --help 显示本帮助');
206
+ }
207
+
208
+ function confirm() {
209
+ if (!process.stdin.isTTY) {
210
+ warn('非交互环境:需显式加 --yes 才会执行卸载。');
211
+ return false;
212
+ }
213
+ process.stdout.write('确认卸载 multica 及其全部本地数据(含 ~/.multica)?[y/N] ');
214
+ const buf = Buffer.alloc(1);
215
+ try {
216
+ const n = fs.readSync(0, buf, 0, 1, null);
217
+ return n > 0 && /^y/i.test(buf.toString());
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+
223
+ function main() {
224
+ const opts = parseArgs(process.argv.slice(2));
225
+ if (opts.help) return printHelp();
226
+
227
+ log('即将卸载 multica(daemon 客户端)');
228
+ log('检测到的 multica 命令入口:');
229
+ const onPath = findOnPath();
230
+ if (onPath.length) onPath.forEach((p) => log(` · ${p}`));
231
+ else log(' · (PATH 中未发现 multica)');
232
+ log('');
233
+
234
+ if (opts.dryRun) {
235
+ log('(--dry-run)将执行:停 daemon → 删配置 → 删独立二进制 → 删命令入口 → 卸 npm 包');
236
+ log(` 配置目录:${configDir()}`);
237
+ log(` 独立二进制:${standaloneBinary()}`);
238
+ const pkgs = installedNpmPkgs();
239
+ log(` npm 全局包:${pkgs.length ? pkgs.map((p) => `${p.name}(${p.prefix})`).join('; ') : '(无)'}`);
240
+ return;
241
+ }
242
+
243
+ if (!confirm()) {
244
+ log('已取消。');
245
+ return;
246
+ }
247
+
248
+ stopDaemon();
249
+ removeConfig(opts.keepConfig);
250
+ removeStandaloneBinary();
251
+ removeShims();
252
+ const failed = uninstallNpmPkgs(installedNpmPkgs());
253
+
254
+ log('');
255
+ if (failed.length) {
256
+ warn(`以下 npm 包未能卸载(多为当前进程占用文件,重开终端后手动执行即可):`);
257
+ failed.forEach((n) => log(` npm uninstall -g ${n}`));
258
+ } else {
259
+ ok('multica 已卸载完成。重开终端以确保 PATH 刷新。');
260
+ }
261
+ }
262
+
263
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asiainfo-sdd/multica-cli",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "数字工厂 daemon 客户端安装帮助包:npm install 时按平台下载二进制并校验并写入部署级配置,不参与 daemon 的使用与管理",
5
5
  "bin": {
6
6
  "multica": "bin/multica.js",