dsh-plugin 2.0.5 → 2.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/client/client.js +312 -306
- package/client/client.js.map +1 -1
- package/lib/services/install/preflight.js +36 -0
- package/lib/services/install/task-queue.js +18 -18
- package/lib/types/services/install/preflight.d.ts +11 -0
- package/package.json +1 -1
- package/src/client/logic/installed.ts +10 -6
- package/src/server/services/install/preflight.ts +37 -0
- package/src/server/services/install/task-queue.ts +17 -17
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安装前置探测:命令是否存在、安装目标是否需要 git。
|
|
3
|
+
*
|
|
4
|
+
* 命令缺失时各平台报错形态不同(Windows 只回 GBK 乱码、POSIX 才抛 spawn ENOENT),
|
|
5
|
+
* 统一在 spawn 前用 which/where 探测,缺失则打 ASCII 标记,前端归类不受编码影响。
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
/** 缺 git 的失败标记:前端按 `[git-missing]` 归类并给「安装 git」指引(乱码免疫)。 */
|
|
9
|
+
export const GIT_MISSING_DETAIL = '[git-missing] the git CLI was not found on this machine (install git and add it to the PATH, then retry)';
|
|
10
|
+
/** 命令存在性探测:which/where 找 PATH 里的命令。
|
|
11
|
+
* 探测自身异常(which/where 不可用等)返回 true,不拦截安装,交给装后校验兜底。 */
|
|
12
|
+
export function commandOnPath(cmd, env) {
|
|
13
|
+
try {
|
|
14
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
15
|
+
const res = spawnSync(probe, [cmd], { env, encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
16
|
+
return res.status === 0 && (res.stdout ?? '').trim().length > 0;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** 该安装目标是否需要 git:github: / git+ / git@ 与 owner/repo 简写都要先 clone;
|
|
23
|
+
* release 直链(https://…releases/download/…tgz)由 pnpm 直接下载,不需要 git。
|
|
24
|
+
* owner/repo 段首必须是字母数字:`./x`、`../x` 这类相对路径是本地目录,不能误判。 */
|
|
25
|
+
export function needsGit(target) {
|
|
26
|
+
const v = target.trim();
|
|
27
|
+
if (/^(github:|git\+|git@)/i.test(v))
|
|
28
|
+
return true;
|
|
29
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9._-]+$/.test(v);
|
|
30
|
+
}
|
|
31
|
+
/** git 源安装前置检查:需要 git 但本机 PATH 里没有 → 返回 [git-missing] 文本;否则 null。 */
|
|
32
|
+
export function gitMissingDetail(target, env) {
|
|
33
|
+
if (!needsGit(target))
|
|
34
|
+
return null;
|
|
35
|
+
return commandOnPath('git', env) ? null : GIT_MISSING_DETAIL;
|
|
36
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 每个任务一次 `dsh plugin --profile <profile> <action> <target>` 子进程,
|
|
8
8
|
* 输出流式收集到任务上;卸载成功时尝试从运行中 loader 即时停用(见 loader.ts)。
|
|
9
9
|
*/
|
|
10
|
-
import { spawn
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
11
|
import { requiredDistributionFiles, resolveBundlePatchFiles } from './package-entry.js';
|
|
12
12
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
13
13
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -20,6 +20,7 @@ import { hasClientUi, isDshPlugin, mountLoadedEntry, removeLoadedEntry } from '.
|
|
|
20
20
|
import { addPendingRestart, clearPendingRestart } from '../profile/pending-restart.js';
|
|
21
21
|
import { addAllowBuildsKey, bundleIncludes, githubRepoOf, parseAllowBuildsKey, profileDirectory } from '../profile/profile.js';
|
|
22
22
|
import { appendLog } from '../log.js';
|
|
23
|
+
import { commandOnPath, gitMissingDetail } from './preflight.js';
|
|
23
24
|
/** 内存任务注册表,按任务 id 索引。 */
|
|
24
25
|
const tasks = new Map();
|
|
25
26
|
let nextTaskId = 1;
|
|
@@ -194,22 +195,6 @@ export function desktopCliBootstrap(entry) {
|
|
|
194
195
|
}
|
|
195
196
|
return null;
|
|
196
197
|
}
|
|
197
|
-
/**
|
|
198
|
-
* dsh 命令存在性探测:which/where 找 PATH 里的 dsh。
|
|
199
|
-
* 命令缺失时各平台报错形态不同(Windows shell 只回 GBK 乱码、POSIX 才抛 spawn ENOENT),
|
|
200
|
-
* 统一在 spawn 前用 which/where 探测,命令不存在则直接失败并打 ASCII 标记 [dsh-missing](乱码免疫)。
|
|
201
|
-
* 探测自身异常(which/where 不可用等)不拦截安装,交给装后校验兜底。
|
|
202
|
-
*/
|
|
203
|
-
function dshOnPath(env) {
|
|
204
|
-
try {
|
|
205
|
-
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
206
|
-
const res = spawnSync(probe, ['dsh'], { env, encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
207
|
-
return res.status === 0 && (res.stdout ?? '').trim().length > 0;
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
198
|
function stopChild(child) {
|
|
214
199
|
if (process.platform === 'win32' && child.pid !== undefined) {
|
|
215
200
|
const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], { stdio: 'ignore' });
|
|
@@ -400,7 +385,7 @@ function spawnMutation(options) {
|
|
|
400
385
|
// 独立 CLI 场景(file: 'dsh',宿主加载器走 node bin 不经过这里):先探测 dsh 是否在 PATH。
|
|
401
386
|
// 命令缺失时各平台报错形态不同(GBK 乱码 / spawn ENOENT),统一前置拦截并打 ASCII 标记
|
|
402
387
|
// [dsh-missing],前端据此精准提示「dsh 未加入 PATH」而不是笼统的插件失败。
|
|
403
|
-
if (invocation.file === 'dsh' && !
|
|
388
|
+
if (invocation.file === 'dsh' && !commandOnPath('dsh', env)) {
|
|
404
389
|
const detail = '[dsh-missing] the dsh CLI was not found on this machine (DeepSeek Harness not installed, or dsh not on the PATH)';
|
|
405
390
|
if (task) {
|
|
406
391
|
task.status = 'failed';
|
|
@@ -411,6 +396,21 @@ function spawnMutation(options) {
|
|
|
411
396
|
resolveOnce({ exitCode: 1, timedOut: false, error: detail, stdout: '', stderr: detail });
|
|
412
397
|
return;
|
|
413
398
|
}
|
|
399
|
+
// git 源安装:pnpm 需要调用 git 解析仓库;Windows 缺 git 只回 GBK 乱码,前端认不出来。
|
|
400
|
+
// 前置探测并打 ASCII 标记 [git-missing],给出装 git 的指引而不是笼统的安装失败。
|
|
401
|
+
if (!isGlobalNpm && action !== 'remove') {
|
|
402
|
+
const gitMissing = gitMissingDetail(target, env);
|
|
403
|
+
if (gitMissing !== null) {
|
|
404
|
+
if (task) {
|
|
405
|
+
task.status = 'failed';
|
|
406
|
+
task.exitCode = null;
|
|
407
|
+
task.progress = 0;
|
|
408
|
+
pushLine(task, gitMissing);
|
|
409
|
+
}
|
|
410
|
+
resolveOnce({ exitCode: 1, timedOut: false, error: gitMissing, stdout: '', stderr: gitMissing });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
414
|
child = spawn(invocation.file, attemptArgs, {
|
|
415
415
|
cwd: invocation.cwd,
|
|
416
416
|
// invocation.env 是「必须叠加」的变量(打包桌面端的 ELECTRON_RUN_AS_NODE):
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 缺 git 的失败标记:前端按 `[git-missing]` 归类并给「安装 git」指引(乱码免疫)。 */
|
|
2
|
+
export declare const GIT_MISSING_DETAIL = "[git-missing] the git CLI was not found on this machine (install git and add it to the PATH, then retry)";
|
|
3
|
+
/** 命令存在性探测:which/where 找 PATH 里的命令。
|
|
4
|
+
* 探测自身异常(which/where 不可用等)返回 true,不拦截安装,交给装后校验兜底。 */
|
|
5
|
+
export declare function commandOnPath(cmd: string, env?: NodeJS.ProcessEnv): boolean;
|
|
6
|
+
/** 该安装目标是否需要 git:github: / git+ / git@ 与 owner/repo 简写都要先 clone;
|
|
7
|
+
* release 直链(https://…releases/download/…tgz)由 pnpm 直接下载,不需要 git。
|
|
8
|
+
* owner/repo 段首必须是字母数字:`./x`、`../x` 这类相对路径是本地目录,不能误判。 */
|
|
9
|
+
export declare function needsGit(target: string): boolean;
|
|
10
|
+
/** git 源安装前置检查:需要 git 但本机 PATH 里没有 → 返回 [git-missing] 文本;否则 null。 */
|
|
11
|
+
export declare function gitMissingDetail(target: string, env?: NodeJS.ProcessEnv): string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 10000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):10000+ 人工精选社区插件,每日更新。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -48,6 +48,12 @@ export interface InstalledItem {
|
|
|
48
48
|
dshCapable: boolean
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** 本地路径安装(file:/link:/绝对或相对路径):身份在本机,不能按 npm 包名认领目录条目。 */
|
|
52
|
+
function isLocalSpec(value: string): boolean {
|
|
53
|
+
const v = value.trim()
|
|
54
|
+
return /^(file|link):/i.test(v) || v.startsWith('.') || v.startsWith('/') || /^[A-Za-z]:[\\/]/.test(v)
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
/** 安装时记录的目录信号(按仓库小写 key 查表)。 */
|
|
52
58
|
function signalOf(repo: string | null, versions: Record<string, InstalledVersionSignal>): InstalledVersionSignal | null {
|
|
53
59
|
if (!repo) return null
|
|
@@ -66,15 +72,13 @@ export function installedNameOf(
|
|
|
66
72
|
for (const [name, spec] of Object.entries(installed)) {
|
|
67
73
|
if (repoFromInstallTarget(spec).toLowerCase() === needle) return name
|
|
68
74
|
}
|
|
69
|
-
// npm
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// 只认 npm 安装的依赖(spec 无法解析出仓库身份):git/本地安装的依赖身份由仓库匹配决定,
|
|
73
|
-
// 包名同名不代表同仓库——否则「git 装 A 仓库」会被「npm 包名同名但仓库不同」的目录条目
|
|
74
|
-
// 误认领成已安装(假阳性),installedItems 里出现同 key 双条目,排序一变化就渲染出幽灵行。
|
|
75
|
+
// 只认 npm 安装的依赖(spec 无法解析出仓库身份、也不是本地路径):git 安装的依赖身份由仓库
|
|
76
|
+
// 匹配决定,file:/link: 安装的本地插件身份在本机 —— 包名同名不代表同仓库,否则「本地装 A」
|
|
77
|
+
// 会被目录里同名的 B 认领,已安装页显示成 B 的信息。
|
|
75
78
|
const pkg = ((plugin.source?.npmPackage || versions[repo.toLowerCase()]?.npmPackage) ?? '').toLowerCase()
|
|
76
79
|
if (pkg) {
|
|
77
80
|
for (const [name, spec] of Object.entries(installed)) {
|
|
81
|
+
if (isLocalSpec(spec)) continue
|
|
78
82
|
if (isRepoLike(repoFromInstallTarget(spec))) continue
|
|
79
83
|
if (name.toLowerCase() === pkg) return name
|
|
80
84
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安装前置探测:命令是否存在、安装目标是否需要 git。
|
|
3
|
+
*
|
|
4
|
+
* 命令缺失时各平台报错形态不同(Windows 只回 GBK 乱码、POSIX 才抛 spawn ENOENT),
|
|
5
|
+
* 统一在 spawn 前用 which/where 探测,缺失则打 ASCII 标记,前端归类不受编码影响。
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from 'node:child_process'
|
|
8
|
+
|
|
9
|
+
/** 缺 git 的失败标记:前端按 `[git-missing]` 归类并给「安装 git」指引(乱码免疫)。 */
|
|
10
|
+
export const GIT_MISSING_DETAIL = '[git-missing] the git CLI was not found on this machine (install git and add it to the PATH, then retry)'
|
|
11
|
+
|
|
12
|
+
/** 命令存在性探测:which/where 找 PATH 里的命令。
|
|
13
|
+
* 探测自身异常(which/where 不可用等)返回 true,不拦截安装,交给装后校验兜底。 */
|
|
14
|
+
export function commandOnPath(cmd: string, env?: NodeJS.ProcessEnv): boolean {
|
|
15
|
+
try {
|
|
16
|
+
const probe = process.platform === 'win32' ? 'where' : 'which'
|
|
17
|
+
const res = spawnSync(probe, [cmd], { env, encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
18
|
+
return res.status === 0 && (res.stdout ?? '').trim().length > 0
|
|
19
|
+
} catch {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 该安装目标是否需要 git:github: / git+ / git@ 与 owner/repo 简写都要先 clone;
|
|
25
|
+
* release 直链(https://…releases/download/…tgz)由 pnpm 直接下载,不需要 git。
|
|
26
|
+
* owner/repo 段首必须是字母数字:`./x`、`../x` 这类相对路径是本地目录,不能误判。 */
|
|
27
|
+
export function needsGit(target: string): boolean {
|
|
28
|
+
const v = target.trim()
|
|
29
|
+
if (/^(github:|git\+|git@)/i.test(v)) return true
|
|
30
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9._-]+$/.test(v)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** git 源安装前置检查:需要 git 但本机 PATH 里没有 → 返回 [git-missing] 文本;否则 null。 */
|
|
34
|
+
export function gitMissingDetail(target: string, env?: NodeJS.ProcessEnv): string | null {
|
|
35
|
+
if (!needsGit(target)) return null
|
|
36
|
+
return commandOnPath('git', env) ? null : GIT_MISSING_DETAIL
|
|
37
|
+
}
|
|
@@ -22,6 +22,7 @@ import { hasClientUi, isDshPlugin, mountLoadedEntry, removeLoadedEntry } from '.
|
|
|
22
22
|
import { addPendingRestart, clearPendingRestart } from '../profile/pending-restart.ts'
|
|
23
23
|
import { addAllowBuildsKey, bundleIncludes, githubRepoOf, parseAllowBuildsKey, profileDirectory } from '../profile/profile.ts'
|
|
24
24
|
import { appendLog } from '../log.ts'
|
|
25
|
+
import { commandOnPath, gitMissingDetail } from './preflight.ts'
|
|
25
26
|
|
|
26
27
|
/** 内存任务注册表,按任务 id 索引。 */
|
|
27
28
|
const tasks = new Map<number, InstallTask>()
|
|
@@ -212,22 +213,6 @@ export function desktopCliBootstrap(entry: string): string | null {
|
|
|
212
213
|
return null
|
|
213
214
|
}
|
|
214
215
|
|
|
215
|
-
/**
|
|
216
|
-
* dsh 命令存在性探测:which/where 找 PATH 里的 dsh。
|
|
217
|
-
* 命令缺失时各平台报错形态不同(Windows shell 只回 GBK 乱码、POSIX 才抛 spawn ENOENT),
|
|
218
|
-
* 统一在 spawn 前用 which/where 探测,命令不存在则直接失败并打 ASCII 标记 [dsh-missing](乱码免疫)。
|
|
219
|
-
* 探测自身异常(which/where 不可用等)不拦截安装,交给装后校验兜底。
|
|
220
|
-
*/
|
|
221
|
-
function dshOnPath(env?: NodeJS.ProcessEnv): boolean {
|
|
222
|
-
try {
|
|
223
|
-
const probe = process.platform === 'win32' ? 'where' : 'which'
|
|
224
|
-
const res = spawnSync(probe, ['dsh'], { env, encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
225
|
-
return res.status === 0 && (res.stdout ?? '').trim().length > 0
|
|
226
|
-
} catch {
|
|
227
|
-
return true
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
216
|
function stopChild(child: ReturnType<typeof spawn>): void {
|
|
232
217
|
if (process.platform === 'win32' && child.pid !== undefined) {
|
|
233
218
|
const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], { stdio: 'ignore' })
|
|
@@ -452,7 +437,7 @@ function spawnMutation(options: {
|
|
|
452
437
|
// 独立 CLI 场景(file: 'dsh',宿主加载器走 node bin 不经过这里):先探测 dsh 是否在 PATH。
|
|
453
438
|
// 命令缺失时各平台报错形态不同(GBK 乱码 / spawn ENOENT),统一前置拦截并打 ASCII 标记
|
|
454
439
|
// [dsh-missing],前端据此精准提示「dsh 未加入 PATH」而不是笼统的插件失败。
|
|
455
|
-
if (invocation.file === 'dsh' && !
|
|
440
|
+
if (invocation.file === 'dsh' && !commandOnPath('dsh', env)) {
|
|
456
441
|
const detail = '[dsh-missing] the dsh CLI was not found on this machine (DeepSeek Harness not installed, or dsh not on the PATH)'
|
|
457
442
|
if (task) {
|
|
458
443
|
task.status = 'failed'
|
|
@@ -463,6 +448,21 @@ function spawnMutation(options: {
|
|
|
463
448
|
resolveOnce({ exitCode: 1, timedOut: false, error: detail, stdout: '', stderr: detail })
|
|
464
449
|
return
|
|
465
450
|
}
|
|
451
|
+
// git 源安装:pnpm 需要调用 git 解析仓库;Windows 缺 git 只回 GBK 乱码,前端认不出来。
|
|
452
|
+
// 前置探测并打 ASCII 标记 [git-missing],给出装 git 的指引而不是笼统的安装失败。
|
|
453
|
+
if (!isGlobalNpm && action !== 'remove') {
|
|
454
|
+
const gitMissing = gitMissingDetail(target, env)
|
|
455
|
+
if (gitMissing !== null) {
|
|
456
|
+
if (task) {
|
|
457
|
+
task.status = 'failed'
|
|
458
|
+
task.exitCode = null
|
|
459
|
+
task.progress = 0
|
|
460
|
+
pushLine(task, gitMissing)
|
|
461
|
+
}
|
|
462
|
+
resolveOnce({ exitCode: 1, timedOut: false, error: gitMissing, stdout: '', stderr: gitMissing })
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
466
|
child = spawn(invocation.file, attemptArgs, {
|
|
467
467
|
cwd: invocation.cwd,
|
|
468
468
|
// invocation.env 是「必须叠加」的变量(打包桌面端的 ELECTRON_RUN_AS_NODE):
|