dsh-agentone 0.5.9 → 0.5.11
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/lib/index.js +14 -3
- package/lib/proc.js +70 -21
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -328,11 +328,17 @@ const BUILTIN_PLUGIN_ALLOW_BUILD = {
|
|
|
328
328
|
'dsh-im': [],
|
|
329
329
|
};
|
|
330
330
|
|
|
331
|
-
/** 安装:白名单校验后转发(desktop 受管 pnpm / 官方 CLI;参数数组,无 shell)。
|
|
331
|
+
/** 安装:白名单校验后转发(desktop 受管 pnpm / 官方 CLI;参数数组,无 shell)。
|
|
332
|
+
* pnpm 11 在 profile 存在被忽略的构建脚本时 add 以 ERR_PNPM_IGNORED_BUILDS
|
|
333
|
+
* 非零退出,但包本身已装上(先装后报)——以落盘为准,装上即成功。 */
|
|
332
334
|
async function installProfilePlugin(config, name, version) {
|
|
333
335
|
assertValidPackage(name, version);
|
|
334
336
|
const target = version ? `${name}@${version}` : name;
|
|
335
|
-
|
|
337
|
+
try {
|
|
338
|
+
await runPluginCli(['add', target, ...(BUILTIN_PLUGIN_ALLOW_BUILD[name] || [])]);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if ((await installedVersion(pluginProfileDir(config), name)) === null) throw error;
|
|
341
|
+
}
|
|
336
342
|
return listProfilePlugins(config);
|
|
337
343
|
}
|
|
338
344
|
|
|
@@ -377,7 +383,12 @@ async function upgradeProfilePlugin(config, name) {
|
|
|
377
383
|
if (latest === current || (current && compareSemver(latest, current) <= 0)) {
|
|
378
384
|
throw new Error(`已是最新版本(v${current ?? latest})`);
|
|
379
385
|
}
|
|
380
|
-
|
|
386
|
+
try {
|
|
387
|
+
await runPluginCli(['add', `${name}@${latest}`, ...(BUILTIN_PLUGIN_ALLOW_BUILD[name] || [])]);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
// 同 install:ERR_PNPM_IGNORED_BUILDS 非零退出但包已装上,不阻断升级
|
|
390
|
+
if ((await installedVersion(dir, name)) !== latest) throw error;
|
|
391
|
+
}
|
|
381
392
|
const after = await installedVersion(dir, name);
|
|
382
393
|
if (after !== latest) {
|
|
383
394
|
// 兑现「插件已升级」提示的前置校验:没升上去就明说,不再假报成功
|
package/lib/proc.js
CHANGED
|
@@ -5,10 +5,66 @@
|
|
|
5
5
|
// (execFile 参数数组、绝不经过 shell)与错误翻译;包名/版本白名单在
|
|
6
6
|
// index.js 的调用侧执行。
|
|
7
7
|
import { execFile } from 'node:child_process';
|
|
8
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { join, dirname, delimiter as pathDelimiter } from 'node:path';
|
|
8
11
|
import { promisify } from 'node:util';
|
|
9
12
|
|
|
10
13
|
const execFileAsync = promisify(execFile);
|
|
11
14
|
|
|
15
|
+
/** 常见安装目录兑底(环境 PATH 之外的补充):GUI 启动的 DSH Desktop 继承
|
|
16
|
+
* launchd 的 PATH(/usr/bin:/bin:…),不含 nvm/homebrew/.local —— 实测
|
|
17
|
+
* 从 Dock 启动时 pnpm/lark-cli 全部 ENOENT,插件页 latest 查不到、CLI
|
|
18
|
+
* 全显示未安装。nvm 取最高 node 版本的 bin;结果进程内缓存。 */
|
|
19
|
+
let extraBinDirsCache = null;
|
|
20
|
+
function extraBinDirs() {
|
|
21
|
+
if (extraBinDirsCache !== null) return extraBinDirsCache;
|
|
22
|
+
const dirs = [];
|
|
23
|
+
const home = homedir();
|
|
24
|
+
try {
|
|
25
|
+
// nvm 目录名形如 v24.18.0,用 compareSemver 取最高版(compareSemver
|
|
26
|
+
// 在本文件后文定义,函数提升可用)
|
|
27
|
+
const versions = readdirSync(join(home, '.nvm/versions/node'))
|
|
28
|
+
.filter((v) => /^v?\d/.test(v))
|
|
29
|
+
.sort((a, b) => compareSemver(a.replace(/^v/, ''), b.replace(/^v/, '')));
|
|
30
|
+
if (versions.length) dirs.push(join(home, '.nvm/versions/node', versions[versions.length - 1], 'bin'));
|
|
31
|
+
} catch {
|
|
32
|
+
// 无 nvm(Windows/其他包管理器):跳过,走后面的通用目录
|
|
33
|
+
}
|
|
34
|
+
dirs.push(join(home, '.local/bin'), '/opt/homebrew/bin', '/usr/local/bin');
|
|
35
|
+
extraBinDirsCache = dirs;
|
|
36
|
+
return dirs;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 命令解析:PATH 里找不到时从兑底目录解析绝对路径(找不到返回原名,
|
|
40
|
+
* 后续 ENOENT 走既有 missing/降级语义)。所有 execFile 调用统一过这里。 */
|
|
41
|
+
export function resolveCommand(tool) {
|
|
42
|
+
const pathDirs = String(process.env.PATH || '').split(pathDelimiter).filter(Boolean);
|
|
43
|
+
const candidates = [];
|
|
44
|
+
for (const dir of [...pathDirs, ...extraBinDirs()]) {
|
|
45
|
+
candidates.push(join(dir, tool));
|
|
46
|
+
if (process.platform === 'win32') candidates.push(join(dir, `${tool}.exe`), join(dir, `${tool}.cmd`));
|
|
47
|
+
}
|
|
48
|
+
return candidates.find((p) => existsSync(p)) || tool;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 统一的子进程执行:解析命令路径,并把解析到的目录前置进子进程 PATH
|
|
52
|
+
* (pnpm/lark-cli 等是 `#!/usr/bin/env node` 脚本,宿主 PATH 贫瘠时
|
|
53
|
+
* 找得到脚本也找不到 node,必须连带注入)。opts.env 只传增量变量。 */
|
|
54
|
+
function execResolved(tool, args, { timeoutMs, cwd, env } = {}) {
|
|
55
|
+
const resolved = resolveCommand(tool);
|
|
56
|
+
const childEnv = { ...process.env, ...(env || {}) };
|
|
57
|
+
if (resolved !== tool) {
|
|
58
|
+
childEnv.PATH = [dirname(resolved), childEnv.PATH || ''].filter(Boolean).join(pathDelimiter);
|
|
59
|
+
}
|
|
60
|
+
return execFileAsync(resolved, args, {
|
|
61
|
+
timeout: timeoutMs,
|
|
62
|
+
killSignal: 'SIGKILL',
|
|
63
|
+
cwd,
|
|
64
|
+
env: childEnv,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
12
68
|
/** 从 pnpm 冗长输出里提取对用户有意义的错误行。 */
|
|
13
69
|
function firstErrorLines(raw) {
|
|
14
70
|
const lines = String(raw).split('\n').map((line) => line.trim()).filter(Boolean);
|
|
@@ -25,13 +81,12 @@ function firstErrorLines(raw) {
|
|
|
25
81
|
* async 函数;execFile 不经过 shell,参数数组中的每个元素都是独立参数,
|
|
26
82
|
* 不存在拼接解释执行。
|
|
27
83
|
*/
|
|
28
|
-
export async function runCliTool(tool, args, { timeoutMs = 300000, cwd } = {}) {
|
|
84
|
+
export async function runCliTool(tool, args, { timeoutMs = 300000, cwd, env } = {}) {
|
|
29
85
|
try {
|
|
30
|
-
const { stdout, stderr } = await
|
|
31
|
-
|
|
32
|
-
killSignal: 'SIGKILL',
|
|
33
|
-
env: process.env,
|
|
86
|
+
const { stdout, stderr } = await execResolved(tool, args, {
|
|
87
|
+
timeoutMs,
|
|
34
88
|
cwd,
|
|
89
|
+
env,
|
|
35
90
|
});
|
|
36
91
|
return { stdout, stderr };
|
|
37
92
|
} catch (error) {
|
|
@@ -147,10 +202,9 @@ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
|
|
|
147
202
|
let stdout;
|
|
148
203
|
let state = 'unauthorized';
|
|
149
204
|
try {
|
|
150
|
-
({ stdout } = await
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
env: tool === 'ccpg-cli' ? { ...process.env, CCPG_FULL_CLI: '1' } : process.env,
|
|
205
|
+
({ stdout } = await execResolved(tool, ['auth', 'status'], {
|
|
206
|
+
timeoutMs,
|
|
207
|
+
env: tool === 'ccpg-cli' ? { CCPG_FULL_CLI: '1' } : undefined,
|
|
154
208
|
}));
|
|
155
209
|
} catch (error) {
|
|
156
210
|
if (error.code === 'ENOENT') return { state: 'missing', version: null, latest: null };
|
|
@@ -160,7 +214,7 @@ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
|
|
|
160
214
|
if (larkStyleAuthorized(stdout) || ccpgStyleAuthorized(stdout)) state = 'authorized';
|
|
161
215
|
|
|
162
216
|
const [version, latest] = await Promise.all([
|
|
163
|
-
|
|
217
|
+
execResolved(tool, ['--version'], { timeoutMs: 10_000 })
|
|
164
218
|
.then((r) => parseCliVersion(r.stdout))
|
|
165
219
|
.catch(() => null),
|
|
166
220
|
cliLatestVersion(tool, timeoutMs).catch(() => null),
|
|
@@ -171,20 +225,15 @@ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
|
|
|
171
225
|
/** 最新版查询:ccpg-cli 官方 check 接口;lark-cli 查 npm 包 dist-tag。 */
|
|
172
226
|
async function cliLatestVersion(tool, timeoutMs) {
|
|
173
227
|
if (tool === 'ccpg-cli') {
|
|
174
|
-
const { stdout } = await
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
228
|
+
const { stdout } = await execResolved(tool, ['update', '--check', '--json'], {
|
|
229
|
+
timeoutMs,
|
|
230
|
+
env: { CCPG_FULL_CLI: '1' },
|
|
231
|
+
});
|
|
179
232
|
const data = JSON.parse(String(stdout).trim());
|
|
180
233
|
return typeof data?.latest_version === 'string' ? data.latest_version : null;
|
|
181
234
|
}
|
|
182
235
|
if (tool === 'lark-cli') {
|
|
183
|
-
const { stdout } = await
|
|
184
|
-
'npm',
|
|
185
|
-
['view', '@larksuite/cli', 'version'],
|
|
186
|
-
{ timeout: timeoutMs, killSignal: 'SIGKILL' },
|
|
187
|
-
);
|
|
236
|
+
const { stdout } = await execResolved('npm', ['view', '@larksuite/cli', 'version'], { timeoutMs });
|
|
188
237
|
return parseCliVersion(stdout);
|
|
189
238
|
}
|
|
190
239
|
return null;
|
|
@@ -198,7 +247,7 @@ async function cliLatestVersion(tool, timeoutMs) {
|
|
|
198
247
|
export async function upgradeCliTool(tool, { timeoutMs = 300_000 } = {}) {
|
|
199
248
|
const { stdout } = await runCliTool(tool, ['update'], {
|
|
200
249
|
timeoutMs,
|
|
201
|
-
env: tool === 'ccpg-cli' ? {
|
|
250
|
+
env: tool === 'ccpg-cli' ? { CCPG_FULL_CLI: '1' } : undefined,
|
|
202
251
|
});
|
|
203
252
|
const lines = String(stdout).split('\n').map((l) => l.trim()).filter(Boolean);
|
|
204
253
|
return lines.slice(-6).join('\n');
|
package/package.json
CHANGED