@ohos-cpf/3rdloop 0.0.4 → 0.0.6
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 +119 -128
- package/lib/cli.js +25 -0
- package/lib/serve.js +280 -0
- package/lib/update.js +46 -6
- package/lib/web-ext.js +454 -0
- package/lib/web.js +664 -0
- package/package.json +2 -1
- package/vendor/Server/CLI/opencode/index.js +2 -2
- package/vendor/Server/DbUse/StorageManager.js +7 -1
- package/vendor/Server/Routes/controllers/FlexRunnerController.js +92 -0
- package/vendor/Server/Routes/routes/flexrunner.js +2 -0
- package/vendor/Server/Skills/arkts-code-use/SKILL.md +270 -0
- package/vendor/Server/Skills/arkts-code-use/assets/TEMPLATES.md +367 -0
- package/vendor/Server/Skills/arkts-code-use/references/API_VERIFICATION.md +144 -0
- package/vendor/Server/Skills/arkts-code-use/references/ARKTS_RULES.md +240 -0
- package/vendor/Server/Skills/arkts-code-use/references/CODE_PATTERNS.md +431 -0
- package/vendor/Server/Skills/arkts-code-use/references/SYNTAX_CHECK_GUIDE.md +164 -0
- package/vendor/Server/Skills/arkts-code-use/scripts/verify-arkts.cjs +428 -0
- package/vendor/Server/Skills/gitcode-repo-fork/SKILL.md +310 -0
- package/vendor/Server/Skills/gitcode-repo-fork/assets/FORK_REPORT_TEMPLATE.md +113 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/FORK_DECISION_GUIDE.md +124 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/GITCODE_FORK_API.md +95 -0
- package/vendor/Server/Skills/gitcode-repo-fork/scripts/gitcode-fork.cjs +285 -0
- package/vendor/VERSION +3 -3
- package/web/css/arktslibrarycheck.css +322 -0
- package/web/css/codecheck.css +464 -0
- package/web/css/flutterlibrarycheck.css +322 -0
- package/web/css/knowledge.css +332 -0
- package/web/css/loop.css +578 -0
- package/web/css/md-reader.css +240 -0
- package/web/css/rnlibrarycheck.css +322 -0
- package/web/css/theme.css +702 -0
- package/web/index.html +713 -0
- package/web/js/arktslibrarycheck.js +1495 -0
- package/web/js/codecheck.js +1098 -0
- package/web/js/flutterlibrarycheck.js +1447 -0
- package/web/js/health.js +69 -0
- package/web/js/knowledge.js +358 -0
- package/web/js/loop.js +1102 -0
- package/web/js/md-reader.js +435 -0
- package/web/js/navigation.js +238 -0
- package/web/js/rnlibrarycheck.js +1462 -0
- package/web/js/stats.js +110 -0
- package/web/js/theme.js +46 -0
- package/web/js/utils.js +228 -0
- package/web/knowledge.html +146 -0
- package/web/loop.html +219 -0
package/lib/serve.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serve.js —— 3rdloop serve 启动 Server HTTP 后端
|
|
3
|
+
*
|
|
4
|
+
* 定位:为前端展示启动现成 Server 后端(复用 Server/Routes/server.js 的
|
|
5
|
+
* createServer,不新增任何 daemon/调度逻辑)。CLI 其余命令仍为嵌入式,不受影响。
|
|
6
|
+
*
|
|
7
|
+
* 行为:
|
|
8
|
+
* - 默认仅启动 HTTP 后端(--port / 3LIB_SERVER_PORT / 3000)
|
|
9
|
+
* - -w/--web 可选:再拉起 Web/server.js 静态前端(反向代理 /api/* 到后端)
|
|
10
|
+
* - 复用 CLI 的环境装配(cli.js 已统一执行 .env 链 + stdout 保护),
|
|
11
|
+
* 与 run/submit 等命令保持一致;json 模式下本模块自行纯化 stdout
|
|
12
|
+
* - 端口冲突 / 启动失败给出明确错误,不静默
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn, execSync } from 'node:child_process';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import process from 'node:process';
|
|
20
|
+
|
|
21
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
export const CLI_ROOT = path.resolve(moduleDir, '..');
|
|
23
|
+
|
|
24
|
+
// ─── vendor/Server 路径解析(与 runner.js 保持一致)────────────────
|
|
25
|
+
let _serverModuleRoot = null;
|
|
26
|
+
function resolveVendorServer(cliRoot) {
|
|
27
|
+
if (_serverModuleRoot) return _serverModuleRoot;
|
|
28
|
+
|
|
29
|
+
const vendorServer = path.join(cliRoot, 'vendor', 'Server');
|
|
30
|
+
if (fs.existsSync(vendorServer)) {
|
|
31
|
+
_serverModuleRoot = vendorServer;
|
|
32
|
+
return vendorServer;
|
|
33
|
+
}
|
|
34
|
+
const repoServer = path.resolve(cliRoot, '..', 'Server');
|
|
35
|
+
if (fs.existsSync(repoServer)) {
|
|
36
|
+
_serverModuleRoot = repoServer;
|
|
37
|
+
return repoServer;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(
|
|
40
|
+
`无法定位核心引擎目录:期望 cli/vendor/Server 或仓库 Server/。` +
|
|
41
|
+
`请先运行 npm run prepack(或 clone 仓库到 cli/ 的上一级)`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 动态导入 Server/Routes/server.js(一手段:vendor 优先、开发期回退仓库源码)。 */
|
|
46
|
+
async function _importServerModule() {
|
|
47
|
+
const serverRoot = resolveVendorServer(CLI_ROOT);
|
|
48
|
+
const url = new URL(`file://${path.join(serverRoot, 'Routes', 'server.js').replace(/\\/g, '/')}`);
|
|
49
|
+
return import(url.href);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Web 静态前端目录(开发期仓库 Web/;发布期 vendor/Web 预留)。 */
|
|
53
|
+
function resolveWebDir() {
|
|
54
|
+
const candidates = [
|
|
55
|
+
path.resolve(CLI_ROOT, '..', 'Web'),
|
|
56
|
+
path.join(CLI_ROOT, 'vendor', 'Web'),
|
|
57
|
+
];
|
|
58
|
+
for (const dir of candidates) {
|
|
59
|
+
if (fs.existsSync(path.join(dir, 'server.js')) && fs.existsSync(path.join(dir, 'public'))) {
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 解析 flags.rest 中 serve 专用参数(不依赖 cli.js 的 parseGlobalFlags,
|
|
68
|
+
* 因为 -w/--web 会被它归入 rest)。
|
|
69
|
+
* @returns {{ web: boolean, port?: number, webPort?: number }}
|
|
70
|
+
*/
|
|
71
|
+
function _parseServeArgs(rest = []) {
|
|
72
|
+
const args = { web: false };
|
|
73
|
+
for (let i = 0; i < rest.length; i++) {
|
|
74
|
+
const arg = rest[i];
|
|
75
|
+
if (arg === '-w' || arg === '--web') args.web = true;
|
|
76
|
+
else if (arg === '--port' || arg === '--web-port') {
|
|
77
|
+
const key = arg === '--port' ? 'port' : 'webPort';
|
|
78
|
+
const n = parseInt(rest[i + 1], 10);
|
|
79
|
+
if (Number.isFinite(n) && n > 0) args[key] = n;
|
|
80
|
+
i++; // 跳过值
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return args;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 3rdloop serve 命令实现。
|
|
88
|
+
* @param {object} opts
|
|
89
|
+
* @param {string[]} opts.rest - flags.rest(含 serve 专用参数)
|
|
90
|
+
* @param {boolean} opts.jsonMode
|
|
91
|
+
* @param {string} [opts.dataDir] - CLI 解析后的数据目录(--data-dir / 3LIB_DATA_DIR / 默认)
|
|
92
|
+
* @returns {Promise<number>} 退出码(serve 为长驻命令,仅 --help/启动失败正常返回)
|
|
93
|
+
*/
|
|
94
|
+
export async function cmdServe({ rest = [], jsonMode = false, dataDir = '' }) {
|
|
95
|
+
// --help / -h:打印用法
|
|
96
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
97
|
+
_printServeHelp();
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const args = _parseServeArgs(rest);
|
|
102
|
+
const serverPort = args.port ?? _envInt('3LIB_SERVER_PORT', 3000);
|
|
103
|
+
const webPort = args.webPort ?? _envInt('3LIB_WEB_PORT', 8080);
|
|
104
|
+
|
|
105
|
+
if (args.web && serverPort === webPort) {
|
|
106
|
+
process.stderr.write(`3rdloop serve: 后端端口与 Web 端口相同 (${serverPort}),请用 --web-port 指定不同端口\n`);
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── 数据目录对齐 CLI(A3):与 run/submit 共用同一 dataDir ──────
|
|
111
|
+
// 通过 env 注入而非逐层传参:Server 内部所有自建 createStorage()
|
|
112
|
+
// 都经 StorageManager 默认根目录解析,env 一处生效(含 FlexRunner /
|
|
113
|
+
// KnowledgeManager / MessageArchive 等惰性自建实例)。
|
|
114
|
+
// 必须在 import Server 模块前设置(覆盖部分模块加载期构造的实例)。
|
|
115
|
+
if (dataDir) {
|
|
116
|
+
process.env['3LIB_DATA_DIR'] = dataDir;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const serverModule = await _importServerModule();
|
|
120
|
+
|
|
121
|
+
// json 模式下纯化 stdout:Server 内部 console.log/info 重定向到 stderr
|
|
122
|
+
let restoreStdout = null;
|
|
123
|
+
if (jsonMode) {
|
|
124
|
+
restoreStdout = _redirectStdoutLogs();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let webChild = null;
|
|
128
|
+
|
|
129
|
+
// 优雅退出:清理 Web 子进程(Windows taskkill 树)后退出
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
if (webChild && webChild.pid != null) {
|
|
132
|
+
try {
|
|
133
|
+
if (process.platform === 'win32') {
|
|
134
|
+
execSync(`taskkill /pid ${webChild.pid} /T /F`, { stdio: 'ignore' });
|
|
135
|
+
} else {
|
|
136
|
+
webChild.kill('SIGTERM');
|
|
137
|
+
}
|
|
138
|
+
} catch { /* 进程可能已退出 */ }
|
|
139
|
+
webChild = null;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
let shuttingDown = false;
|
|
143
|
+
const onSignal = (sig) => {
|
|
144
|
+
if (shuttingDown) return;
|
|
145
|
+
shuttingDown = true;
|
|
146
|
+
cleanup();
|
|
147
|
+
process.exit(sig === 'SIGINT' ? 130 : 143);
|
|
148
|
+
};
|
|
149
|
+
process.once('SIGINT', () => onSignal('SIGINT'));
|
|
150
|
+
process.once('SIGTERM', () => onSignal('SIGTERM'));
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// cliType 由环境决定(shell > .env > 默认 opencode),与 CLI 嵌入式一致;
|
|
154
|
+
// dataDir 已通过 3LIB_DATA_DIR env 注入(见上),Server 内部所有默认
|
|
155
|
+
// createStorage() 均落到用户数据目录;skillDir 沿用包内 vendor 默认
|
|
156
|
+
// (npm 更新自动获得新 SKILL,不与 CLI 的 3LIB_SKILL_DIR 联动)
|
|
157
|
+
const server = await serverModule.createServer({ port: serverPort });
|
|
158
|
+
|
|
159
|
+
if (jsonMode) {
|
|
160
|
+
process.stdout.write(JSON.stringify({
|
|
161
|
+
ok: true,
|
|
162
|
+
type: 'server',
|
|
163
|
+
pid: process.pid,
|
|
164
|
+
port: serverPort,
|
|
165
|
+
url: `http://127.0.0.1:${serverPort}`,
|
|
166
|
+
web: args.web ? { port: webPort, url: `http://localhost:${webPort}` } : null,
|
|
167
|
+
}, null, 2) + '\n');
|
|
168
|
+
} else {
|
|
169
|
+
process.stderr.write(`[3rdloop serve] Server HTTP 后端已启动: http://127.0.0.1:${serverPort}\n`);
|
|
170
|
+
process.stderr.write(`[3rdloop serve] 通过 Ctrl+C 停止\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── 可选启动 Web 前端(默认不拉起,保持聚焦后端) ──
|
|
174
|
+
if (args.web) {
|
|
175
|
+
await _startWebFrontend(webPort, serverPort, jsonMode, (child) => { webChild = child; });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 长驻:保持进程存活直到收到信号
|
|
179
|
+
await new Promise(() => {});
|
|
180
|
+
return 0;
|
|
181
|
+
} catch (err) {
|
|
182
|
+
process.stderr.write(`3rdloop serve 启动失败: ${err.message}\n`);
|
|
183
|
+
return 1;
|
|
184
|
+
} finally {
|
|
185
|
+
restoreStdout?.();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 启动 Web 静态前端(反向代理 /api/* → 后端)。 */
|
|
190
|
+
async function _startWebFrontend(webPort, serverPort, jsonMode, onChild) {
|
|
191
|
+
try {
|
|
192
|
+
if (await _isPortOpen(webPort)) {
|
|
193
|
+
process.stderr.write(`[3rdloop serve] Web 端口 ${webPort} 已被占用,跳过拉起前端(可直接访问该地址)\n`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
} catch { /* 探测失败不阻断 */ }
|
|
197
|
+
|
|
198
|
+
const webDir = resolveWebDir();
|
|
199
|
+
if (!webDir) {
|
|
200
|
+
process.stderr.write('[3rdloop serve] 未找到 Web/ 前端目录,跳过拉起前端\n');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const child = spawn(process.execPath, [
|
|
205
|
+
path.join(webDir, 'server.js'),
|
|
206
|
+
'--port', String(webPort),
|
|
207
|
+
'--backend', `http://127.0.0.1:${serverPort}`,
|
|
208
|
+
], {
|
|
209
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
210
|
+
shell: false,
|
|
211
|
+
detached: false,
|
|
212
|
+
});
|
|
213
|
+
child.stdout?.on('data', (d) => process.stderr.write(`[web] ${d}`));
|
|
214
|
+
child.stderr?.on('data', (d) => process.stderr.write(`[web] ${d}`));
|
|
215
|
+
child.on('error', (err) => {
|
|
216
|
+
process.stderr.write(`[3rdloop serve] 拉起 Web 前端失败: ${err.message}\n`);
|
|
217
|
+
});
|
|
218
|
+
onChild(child);
|
|
219
|
+
if (!jsonMode) {
|
|
220
|
+
process.stderr.write(`[3rdloop serve] Web 前端已启动: http://localhost:${webPort}\n`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** 读取正整数环境变量(非法/缺席返回默认值)。 */
|
|
225
|
+
function _envInt(name, def) {
|
|
226
|
+
const n = parseInt(process.env[name], 10);
|
|
227
|
+
return Number.isFinite(n) && n > 0 ? n : def;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** 探测端口是否有响应(尽力而为,不阻断)。 */
|
|
231
|
+
function _isPortOpen(port, timeoutMs = 1500) {
|
|
232
|
+
return new Promise((resolve) => {
|
|
233
|
+
const controller = new AbortController();
|
|
234
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
235
|
+
fetch(`http://127.0.0.1:${port}/`, { signal: controller.signal })
|
|
236
|
+
.then(() => resolve(true))
|
|
237
|
+
.catch(() => resolve(false))
|
|
238
|
+
.finally(() => clearTimeout(timer));
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** json 模式下把 Server 内部 console.log/info 重定向到 stderr,返回恢复函数。 */
|
|
243
|
+
function _redirectStdoutLogs() {
|
|
244
|
+
const safeStringify = (a) => {
|
|
245
|
+
try { return JSON.stringify(a); } catch { return String(a); }
|
|
246
|
+
};
|
|
247
|
+
const toStderr = (args) => {
|
|
248
|
+
process.stderr.write(args.map((a) => (typeof a === 'string' ? a : safeStringify(a))).join(' ') + '\n');
|
|
249
|
+
};
|
|
250
|
+
const origLog = console.log;
|
|
251
|
+
const origInfo = console.info;
|
|
252
|
+
console.log = (...args) => toStderr(args);
|
|
253
|
+
console.info = (...args) => toStderr(args);
|
|
254
|
+
return () => {
|
|
255
|
+
console.log = origLog;
|
|
256
|
+
console.info = origInfo;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function _printServeHelp() {
|
|
261
|
+
process.stdout.write(`3rdloop serve —— 启动 Server HTTP 后端(供前端展示)
|
|
262
|
+
|
|
263
|
+
用法:
|
|
264
|
+
3rdloop serve [--port N] [-w|--web] [--web-port N]
|
|
265
|
+
|
|
266
|
+
选项:
|
|
267
|
+
--port N 后端端口(默认 3000,可用 3LIB_SERVER_PORT 覆盖)
|
|
268
|
+
-w, --web 同时拉起 Web 前端(默认 8080,可用 --web-port / 3LIB_WEB_PORT 覆盖)
|
|
269
|
+
--web-port N Web 前端端口
|
|
270
|
+
--json 输出结构化启动信息到 stdout(进度/日志走 stderr)
|
|
271
|
+
-h, --help 本帮助
|
|
272
|
+
|
|
273
|
+
说明:
|
|
274
|
+
默认只启动 Server HTTP 后端(http://127.0.0.1:3000),供 Web 前端 / 脚本反向代理调用。
|
|
275
|
+
CLI 的 run/submit/wait 等命令保持嵌入式模式,不经此后端。
|
|
276
|
+
退出: Ctrl+C / SIGTERM 优雅停止(Windows 下会清理 Web 子进程树)。
|
|
277
|
+
`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export default cmdServe;
|
package/lib/update.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* 安全:
|
|
14
14
|
* - registry 默认尊重用户 npm 配置(镜像源),--registry 可覆盖
|
|
15
|
-
* - 全部经
|
|
15
|
+
* - 全部经 runNpm 调 npm:POSIX 保持 execFileSync 直接调用(无 shell),
|
|
16
|
+
* win32 因 npm 是 npm.cmd shim 需 shell:true(cmd 按 PATHEXT 解析),
|
|
17
|
+
* 该分支对用户可控的 --registry 值做白名单校验防 shell 注入
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
import fs from 'node:fs';
|
|
@@ -26,6 +28,44 @@ const CLI_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
|
|
|
26
28
|
const PKG_NAME = '@ohos-cpf/3rdloop';
|
|
27
29
|
const BIN_ABS = path.join(CLI_ROOT, 'bin', '3rdloop.mjs');
|
|
28
30
|
|
|
31
|
+
// ── npm 调用封装 ─────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
// --registry 值来自用户输入,win32 走 shell 前做白名单校验
|
|
34
|
+
const SAFE_REGISTRY_RE = /^https?:\/\/[^\s"'&|<>^();%]+$/;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 跨平台调用 npm。
|
|
38
|
+
*
|
|
39
|
+
* - POSIX(macOS/Linux):shell 为 false,等价于直接 execFileSync('npm', args),
|
|
40
|
+
* macOS 行为与修复前完全一致。
|
|
41
|
+
* - win32:shell 为 true,Node 用 cmd.exe /d /s /c 执行,cmd 按 PATHEXT
|
|
42
|
+
* 找到 npm.cmd(execFileSync 的 CreateProcess 无法直接执行批处理 shim)。
|
|
43
|
+
*
|
|
44
|
+
* 注入防护:仅 win32 分支对 args 中 --registry 的值做 http(s) URL 白名单校验;
|
|
45
|
+
* 其余参数均为代码内常量,无用户输入面。npm 缺失时把原生 ENOENT/EINVAL
|
|
46
|
+
* 转成可读中文提示。
|
|
47
|
+
*/
|
|
48
|
+
export function runNpm(args, opts = {}) {
|
|
49
|
+
const shell = process.platform === 'win32';
|
|
50
|
+
if (shell) {
|
|
51
|
+
const regIdx = args.indexOf('--registry');
|
|
52
|
+
const reg = regIdx !== -1 ? args[regIdx + 1] : null;
|
|
53
|
+
if (reg !== undefined && reg !== null && !SAFE_REGISTRY_RE.test(String(reg))) {
|
|
54
|
+
const err = new Error(`非法 --registry 值(仅允许不含空白/引号/元字符的 http(s) URL): ${reg}`);
|
|
55
|
+
err.code = 'INVALID_REGISTRY';
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return execFileSync('npm', args, { ...opts, shell });
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err && (err.code === 'ENOENT' || err.code === 'EINVAL')) {
|
|
63
|
+
throw new Error('未找到 npm 命令,请确认已安装 Node.js 且 npm 在 PATH 中(npm --version 可验证)');
|
|
64
|
+
}
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
// ── 工具 ─────────────────────────────────────────────────────────
|
|
30
70
|
|
|
31
71
|
/**
|
|
@@ -77,7 +117,7 @@ export async function fetchLatestVersion({ registry } = {}) {
|
|
|
77
117
|
const args = ['view', PKG_NAME, 'version'];
|
|
78
118
|
if (registry) args.push('--registry', registry);
|
|
79
119
|
try {
|
|
80
|
-
const out =
|
|
120
|
+
const out = runNpm(args, {
|
|
81
121
|
encoding: 'utf-8',
|
|
82
122
|
stdio: 'pipe',
|
|
83
123
|
timeout: 30000,
|
|
@@ -103,7 +143,7 @@ export async function fetchLatestVersion({ registry } = {}) {
|
|
|
103
143
|
/** 从用户 npm 配置读取 registry(无 --registry 时使用镜像源)。 */
|
|
104
144
|
function _detectNpmRegistry() {
|
|
105
145
|
try {
|
|
106
|
-
const out =
|
|
146
|
+
const out = runNpm(['config', 'get', 'registry'], {
|
|
107
147
|
encoding: 'utf-8',
|
|
108
148
|
stdio: 'pipe',
|
|
109
149
|
timeout: 10000,
|
|
@@ -155,16 +195,16 @@ export function performUpdate({ latest, registry, force = false, isLink = detect
|
|
|
155
195
|
// 开发态 + --force:先解除链接
|
|
156
196
|
if (isLink && force) {
|
|
157
197
|
process.stderr.write(`[update] 检测到 npm link,先解除链接...\n`);
|
|
158
|
-
|
|
198
|
+
runNpm(['unlink', '-g', PKG_NAME], { encoding: 'utf-8', stdio: 'inherit' });
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
process.stderr.write(`[update] 卸载旧版本(@${PKG_NAME})...\n`);
|
|
162
202
|
try {
|
|
163
|
-
|
|
203
|
+
runNpm(['uninstall', '-g', PKG_NAME], { encoding: 'utf-8', stdio: 'inherit' });
|
|
164
204
|
} catch { /* 卸载失败不阻断重装 */ }
|
|
165
205
|
|
|
166
206
|
process.stderr.write(`[update] 安装最新版本...\n`);
|
|
167
|
-
|
|
207
|
+
runNpm(installArgs, { encoding: 'utf-8', stdio: 'inherit' });
|
|
168
208
|
|
|
169
209
|
return { updated: true, version: latest };
|
|
170
210
|
}
|