dsh-agentone 0.5.8 → 0.5.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.
- package/lib/proc.js +73 -23
- package/package.json +1 -1
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) {
|
|
@@ -74,8 +129,9 @@ export function parseLatestVersionLine(stdout) {
|
|
|
74
129
|
export function compareSemver(a, b) {
|
|
75
130
|
const parse = (v) => {
|
|
76
131
|
const [core, pre] = String(v).split('+')[0].split('-');
|
|
77
|
-
const
|
|
78
|
-
|
|
132
|
+
const raw = core.split('.').map((n) => parseInt(n, 10) || 0);
|
|
133
|
+
while (raw.length < 3) raw.push(0); // 「1.2」补齐为「1.2.0」再比
|
|
134
|
+
return { nums: raw, pre: pre === undefined ? null : pre.split('.') };
|
|
79
135
|
};
|
|
80
136
|
const pa = parse(a);
|
|
81
137
|
const pb = parse(b);
|
|
@@ -146,10 +202,9 @@ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
|
|
|
146
202
|
let stdout;
|
|
147
203
|
let state = 'unauthorized';
|
|
148
204
|
try {
|
|
149
|
-
({ stdout } = await
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
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,
|
|
153
208
|
}));
|
|
154
209
|
} catch (error) {
|
|
155
210
|
if (error.code === 'ENOENT') return { state: 'missing', version: null, latest: null };
|
|
@@ -159,7 +214,7 @@ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
|
|
|
159
214
|
if (larkStyleAuthorized(stdout) || ccpgStyleAuthorized(stdout)) state = 'authorized';
|
|
160
215
|
|
|
161
216
|
const [version, latest] = await Promise.all([
|
|
162
|
-
|
|
217
|
+
execResolved(tool, ['--version'], { timeoutMs: 10_000 })
|
|
163
218
|
.then((r) => parseCliVersion(r.stdout))
|
|
164
219
|
.catch(() => null),
|
|
165
220
|
cliLatestVersion(tool, timeoutMs).catch(() => null),
|
|
@@ -170,20 +225,15 @@ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
|
|
|
170
225
|
/** 最新版查询:ccpg-cli 官方 check 接口;lark-cli 查 npm 包 dist-tag。 */
|
|
171
226
|
async function cliLatestVersion(tool, timeoutMs) {
|
|
172
227
|
if (tool === 'ccpg-cli') {
|
|
173
|
-
const { stdout } = await
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
);
|
|
228
|
+
const { stdout } = await execResolved(tool, ['update', '--check', '--json'], {
|
|
229
|
+
timeoutMs,
|
|
230
|
+
env: { CCPG_FULL_CLI: '1' },
|
|
231
|
+
});
|
|
178
232
|
const data = JSON.parse(String(stdout).trim());
|
|
179
233
|
return typeof data?.latest_version === 'string' ? data.latest_version : null;
|
|
180
234
|
}
|
|
181
235
|
if (tool === 'lark-cli') {
|
|
182
|
-
const { stdout } = await
|
|
183
|
-
'npm',
|
|
184
|
-
['view', '@larksuite/cli', 'version'],
|
|
185
|
-
{ timeout: timeoutMs, killSignal: 'SIGKILL' },
|
|
186
|
-
);
|
|
236
|
+
const { stdout } = await execResolved('npm', ['view', '@larksuite/cli', 'version'], { timeoutMs });
|
|
187
237
|
return parseCliVersion(stdout);
|
|
188
238
|
}
|
|
189
239
|
return null;
|
|
@@ -197,7 +247,7 @@ async function cliLatestVersion(tool, timeoutMs) {
|
|
|
197
247
|
export async function upgradeCliTool(tool, { timeoutMs = 300_000 } = {}) {
|
|
198
248
|
const { stdout } = await runCliTool(tool, ['update'], {
|
|
199
249
|
timeoutMs,
|
|
200
|
-
env: tool === 'ccpg-cli' ? {
|
|
250
|
+
env: tool === 'ccpg-cli' ? { CCPG_FULL_CLI: '1' } : undefined,
|
|
201
251
|
});
|
|
202
252
|
const lines = String(stdout).split('\n').map((l) => l.trim()).filter(Boolean);
|
|
203
253
|
return lines.slice(-6).join('\n');
|
package/package.json
CHANGED