dsh-plugin 1.3.4 → 1.3.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.en.md +38 -1
- package/README.md +38 -1
- package/client/client.js +449 -410
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +9 -5
- package/lib/services/install/npm-resolve.js +3 -1
- package/lib/services/install/task-queue.js +29 -15
- package/lib/services/profile/profile.js +4 -3
- package/lib/services/settings.js +1 -1
- package/lib/types/services/install/install-types.d.ts +2 -2
- package/lib/types/services/install/npm-resolve.d.ts +3 -1
- package/lib/types/services/install/task-queue.d.ts +3 -3
- package/lib/types/services/settings.d.ts +2 -2
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +13 -7
- package/src/client/components/modals/NotificationsModal.tsx +9 -3
- package/src/client/components/views/CustomInstallView.tsx +35 -14
- package/src/client/components/views/DiagnosticsView.tsx +7 -3
- package/src/client/components/views/SettingsView.tsx +20 -33
- package/src/client/hooks/useSettings.ts +3 -3
- package/src/client/hooks/useTaskQueue.ts +12 -6
- package/src/client/locales.ts +31 -15
- package/src/client/logic/failures.ts +3 -0
- package/src/client/logic/renderMarkdown.ts +9 -5
- package/src/client/logic/urls.ts +12 -1
- package/src/client/styles/CustomInstallView.module.css +25 -0
- package/src/client/styles/Dropdown.module.css +10 -0
- package/src/client/styles/Modal.module.css +11 -0
- package/src/server/http/routes.ts +9 -4
- package/src/server/services/install/install-types.ts +2 -2
- package/src/server/services/install/npm-resolve.ts +3 -1
- package/src/server/services/install/task-queue.ts +36 -20
- package/src/server/services/profile/profile.ts +4 -3
- package/src/server/services/settings.ts +3 -3
|
@@ -14,10 +14,10 @@ import { useCallback, useEffect, useState } from 'react'
|
|
|
14
14
|
export interface HubSettings {
|
|
15
15
|
/** 启动时检查目录插件更新,发现可更新时在通知中心提示(安装不自动进行) */
|
|
16
16
|
checkUpdatesOnStart: boolean
|
|
17
|
-
/** npm 镜像源 registry 地址(空串 = 官方 https://registry.npmjs.org) */
|
|
18
|
-
npmRegistry: string
|
|
19
17
|
/** HTTP(S) 代理地址,用于 npm / git / 目录请求(空串 = 直连) */
|
|
20
18
|
proxy: string
|
|
19
|
+
/** npm 镜像源 registry 地址(空串 = 未配置,跟随用户本机 npm 配置,不注入任何 registry) */
|
|
20
|
+
npmRegistry: string
|
|
21
21
|
/** 启用命令行安装 NPM 包(默认开) */
|
|
22
22
|
enableNpmInstall: boolean
|
|
23
23
|
/** 启用命令行安装 GitHub 源码(默认开) */
|
|
@@ -30,8 +30,8 @@ export interface HubSettings {
|
|
|
30
30
|
|
|
31
31
|
export const DEFAULT_SETTINGS: HubSettings = {
|
|
32
32
|
checkUpdatesOnStart: true,
|
|
33
|
-
npmRegistry: '',
|
|
34
33
|
proxy: '',
|
|
34
|
+
npmRegistry: '',
|
|
35
35
|
enableNpmInstall: true,
|
|
36
36
|
enableGitInstall: true,
|
|
37
37
|
enableDshInstall: true,
|
|
@@ -28,6 +28,9 @@ export interface QueueTask {
|
|
|
28
28
|
desc?: string
|
|
29
29
|
/** 所属插件仓库(owner/repo):失败时错误弹窗据此提供「去仓库反馈」入口;卸载为 null */
|
|
30
30
|
repo: string | null
|
|
31
|
+
/** 实际动作(add/update/remove):成功通知据此区分「安装成功」与「更新成功」。
|
|
32
|
+
* 服务端 /active 带回真实动作;本地乐观入队按 update 标记推断。 */
|
|
33
|
+
action?: 'add' | 'update' | 'remove'
|
|
31
34
|
/** 安装时的目录信号快照(版本 + 仓库更新时间):任务成功后在本地记录,供「有更新」比对 */
|
|
32
35
|
version?: string
|
|
33
36
|
updatedAt?: string
|
|
@@ -63,11 +66,12 @@ export interface TaskQueueOptions {
|
|
|
63
66
|
t: Translate
|
|
64
67
|
refreshInstalled: () => void
|
|
65
68
|
/** 任务成功完成:viaModal 表示任务对应当前打开弹窗(弹窗切结果视图),否则走 Toast;repo 供成功通知记录;
|
|
66
|
-
* needsRestart 表示该任务完成后是否仍需宿主重启(结果视图据此给「重启 / 仅完成」)。
|
|
67
|
-
|
|
69
|
+
* needsRestart 表示该任务完成后是否仍需宿主重启(结果视图据此给「重启 / 仅完成」)。
|
|
70
|
+
* update 表示实际执行的是更新(覆盖重装)而非首次安装:通知中心据此显示「更新成功」而非「安装成功」。 */
|
|
71
|
+
onInstallDone: (viaModal: boolean, repo: string | null, needsRestart: boolean, update?: boolean) => void
|
|
68
72
|
onUninstallDone: (viaModal: boolean, repo: string | null, needsRestart: boolean) => void
|
|
69
|
-
/** 任务失败:完整输出或兜底文案 + 所属插件仓库 + 操作类型(安装/卸载)+ 实际执行的安装命令(issue 预填用,可缺省)+ 尝试过的安装方式(npm
|
|
70
|
-
onError: (message: string, repo: string | null, kind: 'install' | 'uninstall', command?: string, attempts?: string[]) => void
|
|
73
|
+
/** 任务失败:完整输出或兜底文案 + 所属插件仓库 + 操作类型(安装/卸载)+ 实际执行的安装命令(issue 预填用,可缺省)+ 尝试过的安装方式(npm 反查/执行命令,可缺省)+ 是否更新。 */
|
|
74
|
+
onError: (message: string, repo: string | null, kind: 'install' | 'uninstall', command?: string, attempts?: string[], update?: boolean) => void
|
|
71
75
|
/** 当前打开的安装/卸载弹窗插件:用于在弹窗内匹配进行中的任务。 */
|
|
72
76
|
installPlugin: HubPlugin | null
|
|
73
77
|
/** 当前打开的自定义安装(命令行)确认弹窗目标:与应用商店同一弹窗机制,匹配进行中的 custom 安装任务。 */
|
|
@@ -195,14 +199,14 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
195
199
|
// 同时缓存展示信息,待重启列表(服务端已登记)合并时直接补齐简介/版本
|
|
196
200
|
void syncInstalledVersion(q.repo, q.version, q.updatedAt)
|
|
197
201
|
pendingInfoRef.current.set(q.target, { desc: q.desc, version: q.version })
|
|
198
|
-
onInstallDone(modalTaskRef.current === q.id, q.repo, q.needsRestart)
|
|
202
|
+
onInstallDone(modalTaskRef.current === q.id, q.repo, q.needsRestart, q.action === 'update')
|
|
199
203
|
}
|
|
200
204
|
} else {
|
|
201
205
|
// 失败:完整展示全部输出行(最新在前,逆序为日志阅读顺序),不裁剪
|
|
202
206
|
const detail = lines.length > 0
|
|
203
207
|
? [...lines].reverse().join('\n')
|
|
204
208
|
: q.kind === 'uninstall' ? t('uninstallFail') : t('installFail')
|
|
205
|
-
onError(detail, q.repo, q.kind, q.command, q.attempts)
|
|
209
|
+
onError(detail, q.repo, q.kind, q.command, q.attempts, q.action === 'update')
|
|
206
210
|
}
|
|
207
211
|
maybeStopPoll()
|
|
208
212
|
}
|
|
@@ -367,6 +371,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
367
371
|
kind: (isRemove ? 'uninstall' : 'install') as 'install' | 'uninstall',
|
|
368
372
|
target: isRemove ? fallback : (display || fallback),
|
|
369
373
|
repo: isRemove ? null : (display || fallback),
|
|
374
|
+
action: (a.action === 'add' || a.action === 'update' || a.action === 'remove') ? a.action : 'add',
|
|
370
375
|
status: a.status === 'running' ? 'running' : 'pending',
|
|
371
376
|
progress: typeof a.progress === 'number' ? a.progress : 0,
|
|
372
377
|
lines: Array.isArray(a.lines) ? (a.lines as string[]) : [],
|
|
@@ -421,6 +426,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
421
426
|
version: input.version, updatedAt: input.updatedAt,
|
|
422
427
|
status: 'running', progress: 0, lines: [], optimistic: true,
|
|
423
428
|
command: input.command,
|
|
429
|
+
action: input.update ? 'update' : 'add',
|
|
424
430
|
// 服务端登记后 /active 会带回真实尝试记录(npm 反查 + 执行命令),先占位空列表
|
|
425
431
|
attempts: [], needsRestart: true,
|
|
426
432
|
}])
|
package/src/client/locales.ts
CHANGED
|
@@ -56,13 +56,14 @@ export const zh = {
|
|
|
56
56
|
gitInstallInvalid: '请输入 GitHub 仓库地址,例如:owner/repo 或 https://github.com/owner/repo',
|
|
57
57
|
dshCmdLabel: 'DeepSeek Harness 命令',
|
|
58
58
|
dshCmdPlaceholder: '粘贴官方 dsh plugin 命令',
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
dshCmdInsertHubUpdate: '插入更新命令',
|
|
60
|
+
dshCmdExample: '例如:dsh plugin --profile web add github:owner/repo,或 dsh plugin --profile web update dsh-plugin',
|
|
61
|
+
dshCmdInvalid: '请输入官方 dsh plugin 命令(--profile 必填,支持 add / update)。例如:dsh plugin --profile web update dsh-plugin',
|
|
61
62
|
installHelp: '帮助',
|
|
62
63
|
helpModalTitle: '支持的命令格式',
|
|
63
64
|
npmInstallHelp: 'lodash、@scope/pkg\t直接输入包名\nnpm install lodash\nnpm i lodash\npnpm add @scope/pkg\npnpm i @scope/pkg\nnpm install -g @scope/cli\t全局安装 CLI 工具,不进「已安装」列表',
|
|
64
65
|
gitInstallHelp: 'owner/repo\nhttps://github.com/owner/repo\ngithub:owner/repo\ngit+https://github.com/owner/repo.git\ngit@github.com:owner/repo.git',
|
|
65
|
-
dshCmdHelp: 'dsh plugin --profile web add <目标>\ndsh plugin --profile=web add <目标>\n\t--profile 必填,官方 CLI 无 -p 简写;<目标> 可为 npm 包名或 github:owner/repo',
|
|
66
|
+
dshCmdHelp: 'dsh plugin --profile web add <目标>\t安装\ndsh plugin --profile web update <目标>\t更新已安装目标到最新版本\ndsh plugin --profile=web add <目标>\ndsh plugin --profile=web update <目标>\n\t--profile 必填,官方 CLI 无 -p 简写;<目标> 可为 npm 包名或 github:owner/repo;卸载请用「已安装」列表',
|
|
66
67
|
globalNpmHint: '将全局安装 CLI 工具(npm install -g),不会出现在「已安装」列表,无需重启宿主',
|
|
67
68
|
installCliBtn: '安装',
|
|
68
69
|
// 安装通道关闭(安全信任开关关掉后,对应卡片禁用并引导去设置打开)
|
|
@@ -107,14 +108,15 @@ export const zh = {
|
|
|
107
108
|
settingsUpdateDesc: '更新检查方式与安装走到的网络通道',
|
|
108
109
|
settingsCheckOnStart: '启动时检查更新',
|
|
109
110
|
settingsCheckOnStartDesc: '打开插件中心时检查已安装插件是否有新版,发现更新会提示',
|
|
111
|
+
settingsProxy: 'HTTP 代理',
|
|
112
|
+
settingsProxyDesc: 'npm / git / 目录数据请求统一走该代理;留空直连',
|
|
110
113
|
settingsMirror: 'npm 镜像源',
|
|
111
|
-
settingsMirrorDesc: 'npm
|
|
114
|
+
settingsMirrorDesc: '默认跟随本机 npm 配置;官方源慢可换国内镜像',
|
|
115
|
+
mirrorNone: '未配置(跟随本机 npm 配置)',
|
|
112
116
|
mirrorOfficial: '官方源 registry.npmjs.org',
|
|
113
117
|
mirrorNpmmirror: '阿里云 npmmirror',
|
|
114
118
|
mirrorTencent: '腾讯云镜像',
|
|
115
|
-
|
|
116
|
-
settingsProxy: 'HTTP 代理',
|
|
117
|
-
settingsProxyDesc: 'npm / git / 目录数据请求统一走该代理;留空直连',
|
|
119
|
+
mirrorTsinghua: '清华大学 TUNA 镜像',
|
|
118
120
|
settingsSecurity: '安全信任',
|
|
119
121
|
settingsSecurityDesc: '命令行安装的三个通道开关:NPM 包、GitHub 源码与 DSH 命令',
|
|
120
122
|
settingsEnableNpm: '启用 NPM 安装',
|
|
@@ -180,6 +182,8 @@ export const zh = {
|
|
|
180
182
|
settingsDiagFail: '不可达',
|
|
181
183
|
// 系统诊断通道列表:一键全量诊断 + 一行一个,可点重测
|
|
182
184
|
diagNpm: 'npm 源',
|
|
185
|
+
// 诊断页 npm 行:未配置镜像时的展示(探测目标仍为官方源,代表本机无配置时的回退)
|
|
186
|
+
diagNpmUnset: '未配置(跟随本机 npm 配置)',
|
|
183
187
|
diagGithub: 'GitHub',
|
|
184
188
|
diagCatalog: '目录站点',
|
|
185
189
|
diagOk: 'OK',
|
|
@@ -230,6 +234,8 @@ export const zh = {
|
|
|
230
234
|
installNow: '直接安装',
|
|
231
235
|
installing: '安装中…',
|
|
232
236
|
installDone: '安装成功',
|
|
237
|
+
// 更新成功(通知中心区分安装/更新):更新 = 已安装目标覆盖重装到最新,与首次安装不同语义
|
|
238
|
+
updateDone: '更新成功',
|
|
233
239
|
installResultTitle: '插件安装成功',
|
|
234
240
|
installResultDesc: '',
|
|
235
241
|
// 更新语义:已安装插件点「更新」走同一条 add 命令原位覆盖重装,弹窗文案区分安装/更新
|
|
@@ -274,6 +280,8 @@ export const zh = {
|
|
|
274
280
|
errorTitle: '操作失败',
|
|
275
281
|
errorTitleInstall: '安装失败',
|
|
276
282
|
errorTitleUninstall: '卸载失败',
|
|
283
|
+
// 更新失败(通知中心区分安装/更新):更新目标覆盖重装失败,与首次安装失败不同语义
|
|
284
|
+
errorTitleUpdate: '更新失败',
|
|
277
285
|
errorPlugin: '插件',
|
|
278
286
|
errorClose: '知道了',
|
|
279
287
|
errorCopy: '复制错误信息',
|
|
@@ -381,13 +389,14 @@ export const en = {
|
|
|
381
389
|
gitInstallInvalid: 'Enter a GitHub repository address, e.g. owner/repo or https://github.com/owner/repo',
|
|
382
390
|
dshCmdLabel: 'DeepSeek Harness command',
|
|
383
391
|
dshCmdPlaceholder: 'Paste an official dsh plugin command',
|
|
384
|
-
|
|
385
|
-
|
|
392
|
+
dshCmdInsertHubUpdate: 'Insert update command',
|
|
393
|
+
dshCmdExample: 'e.g. dsh plugin --profile web add github:owner/repo, or dsh plugin --profile web update dsh-plugin',
|
|
394
|
+
dshCmdInvalid: 'Paste an official dsh plugin command (--profile required, add / update), e.g. dsh plugin --profile web update dsh-plugin',
|
|
386
395
|
installHelp: 'Help',
|
|
387
396
|
helpModalTitle: 'Supported command formats',
|
|
388
397
|
npmInstallHelp: 'lodash or @scope/pkg\tjust type the package name\nnpm install lodash\nnpm i lodash\npnpm add @scope/pkg\npnpm i @scope/pkg\nnpm install -g @scope/cli\tinstalls the CLI globally, not shown in Installed',
|
|
389
398
|
gitInstallHelp: 'owner/repo\nhttps://github.com/owner/repo\ngithub:owner/repo\ngit+https://github.com/owner/repo.git\ngit@github.com:owner/repo.git',
|
|
390
|
-
dshCmdHelp: 'dsh plugin --profile web add <target>\ndsh plugin --profile=web add <target>\n\t--profile is required and there is no -p shorthand; <target> can be an npm package or github:owner/repo',
|
|
399
|
+
dshCmdHelp: 'dsh plugin --profile web add <target>\tinstall\ndsh plugin --profile web update <target>\tupdate an installed target to the latest version\ndsh plugin --profile=web add <target>\ndsh plugin --profile=web update <target>\n\t--profile is required and there is no -p shorthand; <target> can be an npm package or github:owner/repo; uninstall from the Installed list',
|
|
391
400
|
globalNpmHint: 'This installs CLI tools globally (npm install -g). They won\'t appear in Installed and no host restart is needed.',
|
|
392
401
|
installCliBtn: 'Install',
|
|
393
402
|
// Install channel disabled (security-trust toggle off → card disabled, guides to settings)
|
|
@@ -434,14 +443,15 @@ export const en = {
|
|
|
434
443
|
settingsCheckOnStartDesc: 'Check installed plugins for new versions when the hub opens; you will be notified when updates are found',
|
|
435
444
|
settingsAutoInstall: 'Auto-install updates',
|
|
436
445
|
settingsAutoInstallDesc: 'Install detected updates in the background without asking (a host restart applies them)',
|
|
437
|
-
|
|
438
|
-
|
|
446
|
+
settingsProxy: 'HTTP proxy',
|
|
447
|
+
settingsProxyDesc: 'Route npm, git and catalog requests through this proxy; leave empty to connect directly',
|
|
448
|
+
settingsMirror: 'npm mirror',
|
|
449
|
+
settingsMirrorDesc: 'Unset by default — follows local npm config; pick a domestic mirror if slow',
|
|
450
|
+
mirrorNone: 'Unset (follow local npm config)',
|
|
439
451
|
mirrorOfficial: 'Official registry.npmjs.org',
|
|
440
452
|
mirrorNpmmirror: 'Alibaba npmmirror',
|
|
441
453
|
mirrorTencent: 'Tencent Cloud mirror',
|
|
442
|
-
|
|
443
|
-
settingsProxy: 'HTTP proxy',
|
|
444
|
-
settingsProxyDesc: 'Route npm, git and catalog requests through this proxy; leave empty to connect directly',
|
|
454
|
+
mirrorTsinghua: 'Tsinghua TUNA mirror',
|
|
445
455
|
settingsSecurity: 'Security & Trust',
|
|
446
456
|
settingsSecurityDesc: 'Toggles for the three command-line install channels: npm packages, GitHub sources and DSH commands',
|
|
447
457
|
settingsEnableNpm: 'Enable npm installs',
|
|
@@ -507,6 +517,8 @@ export const en = {
|
|
|
507
517
|
settingsDiagFail: 'Unreachable',
|
|
508
518
|
// System diagnostics channel list: one-click full check + one row per channel, click to re-probe
|
|
509
519
|
diagNpm: 'npm registry',
|
|
520
|
+
// Diagnostics npm row: shown when no mirror is configured (probe still targets the official registry, the fallback)
|
|
521
|
+
diagNpmUnset: 'Unset (follow local npm config)',
|
|
510
522
|
diagGithub: 'GitHub',
|
|
511
523
|
diagCatalog: 'Catalog site',
|
|
512
524
|
diagOk: 'OK',
|
|
@@ -557,6 +569,8 @@ export const en = {
|
|
|
557
569
|
installNow: 'Install',
|
|
558
570
|
installing: 'Installing…',
|
|
559
571
|
installDone: 'Installed',
|
|
572
|
+
// Updated (notification center distinguishes install/update): update = in-place reinstall to latest
|
|
573
|
+
updateDone: 'Updated',
|
|
560
574
|
installResultTitle: 'Plugin installed successfully',
|
|
561
575
|
installResultDesc: '',
|
|
562
576
|
// Update semantics: the "update" action re-runs add in place — copy differentiates install vs update
|
|
@@ -600,6 +614,8 @@ export const en = {
|
|
|
600
614
|
errorTitle: 'Operation failed',
|
|
601
615
|
errorTitleInstall: 'Install failed',
|
|
602
616
|
errorTitleUninstall: 'Removal failed',
|
|
617
|
+
// Update failed (notification center distinguishes install/update): update target reinstall failed
|
|
618
|
+
errorTitleUpdate: 'Update failed',
|
|
603
619
|
errorPlugin: 'Plugin',
|
|
604
620
|
errorClose: 'Got it',
|
|
605
621
|
errorCopy: 'Copy error',
|
|
@@ -18,6 +18,9 @@ export interface NotificationRecord {
|
|
|
18
18
|
ok: boolean
|
|
19
19
|
/** owner/repo(可能为空:请求层失败但拿不到仓库时) */
|
|
20
20
|
repo: string
|
|
21
|
+
/** 实际动作(install/update/uninstall):成功/失败通知据此区分「安装成功」与「更新成功」。
|
|
22
|
+
* 缺省按 kind 兜底(历史记录可能没有该字段)。更新提醒(kind='update')不用此字段。 */
|
|
23
|
+
action?: 'install' | 'update' | 'uninstall'
|
|
21
24
|
/** 失败时的完整错误日志;成功记录为空串 */
|
|
22
25
|
message: string
|
|
23
26
|
/** 更新提醒:目录里的新版本号(展示用;其余通知省略) */
|
|
@@ -33,11 +33,15 @@ function inline(s: string): string {
|
|
|
33
33
|
return `\u0000${codeSpans.length - 1}\u0000`
|
|
34
34
|
})
|
|
35
35
|
// 图片 :仅放行 http(s),用于展示反馈群二维码等;limited by CSS
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// 文件名为 dsh-plugin-user-group-qr- 前缀的视为用户反馈群二维码,固定 250×250 不撑破弹窗。
|
|
37
|
+
out = out.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (m, alt, url) => {
|
|
38
|
+
const src = String(url)
|
|
39
|
+
if (!/^https?:\/\//i.test(src) || /[\u0000]/.test(src)) return m
|
|
40
|
+
if (/\/dsh-plugin-user-group-qr-[^/]*$/i.test(src)) {
|
|
41
|
+
return `<img src="${src}" alt="${String(alt)}" width="250" height="250" loading="lazy">`
|
|
42
|
+
}
|
|
43
|
+
return `<img src="${src}" alt="${String(alt)}" loading="lazy">`
|
|
44
|
+
})
|
|
41
45
|
// 链接 [text](url):仅放行 http(s),新窗口打开
|
|
42
46
|
out = out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (m, text, url) =>
|
|
43
47
|
/^https?:\/\//i.test(String(url)) && !/[\u0000]/.test(String(url))
|
package/src/client/logic/urls.ts
CHANGED
|
@@ -33,6 +33,15 @@ export function pluginSiteUrl(repo: string): string {
|
|
|
33
33
|
return `${SITE_URL}plugins/${repo}`
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** 根据实际执行的命令判定失败动作标签(安装/更新/卸载):
|
|
37
|
+
* 命令形如 `dsh plugin --profile web add|update|remove <target>`,
|
|
38
|
+
* 出现 remove/update 动词即对应归类,其余(add、全局 npm 命令、未知)一律算安装。 */
|
|
39
|
+
function actionLabelOf(command: string | undefined): string {
|
|
40
|
+
if (command && /\s+remove\s+/i.test(command)) return 'Remove'
|
|
41
|
+
if (command && /\s+update\s+/i.test(command)) return 'Update'
|
|
42
|
+
return 'Install'
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
/**
|
|
37
46
|
* 一键反馈 GitHub Issue 的预填链接:标题带「来自 dsh-plugin.org」标识,正文只带
|
|
38
47
|
* 核心信息 —— 原因判定 + 关键错误代码 + 尝试过的安装方式 + 宿主机器环境快照 +
|
|
@@ -43,7 +52,9 @@ export function pluginSiteUrl(repo: string): string {
|
|
|
43
52
|
* 组织 scope 与 GitHub 用户名不一致时也能直接指认正确的 npm 包名。
|
|
44
53
|
*/
|
|
45
54
|
export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | null, command?: string, attempts?: string[]): string {
|
|
46
|
-
|
|
55
|
+
// 标题按实际执行的动作区分(安装/更新/卸载),避免作者误读 —— 旧版固定写死
|
|
56
|
+
// 「Install/Remove」会让更新失败被当成删除失败。
|
|
57
|
+
const title = `[dsh-plugin.org | dsh-plugin-hub] ${actionLabelOf(command)} failed: ${repo}`
|
|
47
58
|
// 原因判定:按失败类型归类,便于作者判断问题是否出在插件侧。
|
|
48
59
|
// [packaging](预检/装后校验拦截)单独列:无论走 npm 还是 git 通道,都是分发物不完整
|
|
49
60
|
// (package.json 声明的入口文件在发布物里缺失),作者照此补齐即可。
|
|
@@ -84,6 +84,31 @@
|
|
|
84
84
|
border-color: var(--hub-brand-border-soft);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/* 命令卡片标题旁的「一键插入更新命令」:右对齐小号描边按钮,弱化但可点,hover 提亮品牌色 */
|
|
88
|
+
.installInsertBtn {
|
|
89
|
+
flex-shrink: 0;
|
|
90
|
+
margin-left: auto;
|
|
91
|
+
font-family: inherit;
|
|
92
|
+
font-size: 11px;
|
|
93
|
+
line-height: 16px;
|
|
94
|
+
font-weight: 500;
|
|
95
|
+
color: var(--hub-text-secondary);
|
|
96
|
+
background: transparent;
|
|
97
|
+
border: 1px solid var(--hub-border-2);
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
padding: 2px 9px;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
white-space: nowrap;
|
|
102
|
+
user-select: none;
|
|
103
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.installInsertBtn:hover {
|
|
107
|
+
color: var(--hub-brand);
|
|
108
|
+
border-color: var(--hub-brand-border-soft);
|
|
109
|
+
background: var(--hub-brand-tint);
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
/* 文本框上方的示例:灰字小字,告诉用户该通道的推荐写法 */
|
|
88
113
|
.installExample {
|
|
89
114
|
font-size: 11px;
|
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
flex-shrink: 0;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/* 全宽变体:设置页「独占一行」的下拉与文本框同宽,按钮铺满整行 */
|
|
16
|
+
.dropdownFill {
|
|
17
|
+
width: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.dropdownFill > .dropdownBtn {
|
|
21
|
+
width: 100%;
|
|
22
|
+
justify-content: space-between;
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
/* 触发器:工具栏按钮风格 —— 细边框 + 次级灰底,与通知入口按钮同一视觉语言,
|
|
16
26
|
只占一个按钮位,给工具栏后续新增按钮留空间 */
|
|
17
27
|
.dropdownBtn {
|
|
@@ -1355,6 +1355,17 @@
|
|
|
1355
1355
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
1356
1356
|
}
|
|
1357
1357
|
|
|
1358
|
+
/* 用户反馈群二维码(renderMarkdown 对 dsh-plugin-user-group-qr- 前缀图片加了
|
|
1359
|
+
width/height="250"):固定 250×250 正方形。必须显式覆盖通用 max-height 上限 ——
|
|
1360
|
+
CSS max-height 对 height 有最终裁决权,不覆盖的话高度被压成 220 而宽度保持 250,
|
|
1361
|
+
图片就会变成非正方形(看起来像「250×250 没生效」) */
|
|
1362
|
+
.aboutContent img[width="250"] {
|
|
1363
|
+
width: 250px;
|
|
1364
|
+
height: 250px;
|
|
1365
|
+
max-height: 250px;
|
|
1366
|
+
object-fit: contain;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1358
1369
|
/* 更新时间小字:弱化展示在内容区下方 */
|
|
1359
1370
|
.aboutMeta {
|
|
1360
1371
|
font-size: 11px;
|
|
@@ -184,12 +184,12 @@ function hostEnv(profile: string): Record<string, string | null> {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
/** 安装/卸载 CLI 的子进程环境:透传宿主环境,并按设置注入
|
|
187
|
+
/** 安装/卸载 CLI 的子进程环境:透传宿主环境,并按设置注入 HTTP(S) 代理。
|
|
188
|
+
* npm registry 不再注入 —— 安装完全沿用用户本机 npm 配置(~/.npmrc / 全局配置)。
|
|
188
189
|
* 代理优先级:设置里的代理 → 系统代理(macOS scutil / Windows 注册表)→ 宿主 env 原有值。
|
|
189
190
|
* 使安装通道与诊断使用相同的代理来源。 */
|
|
190
191
|
function mutationEnv(settings: HubSettings): NodeJS.ProcessEnv {
|
|
191
192
|
const env: NodeJS.ProcessEnv = { ...process.env, CI: 'true' }
|
|
192
|
-
if (settings.npmRegistry !== '') env.npm_config_registry = settings.npmRegistry
|
|
193
193
|
const proxy = settings.proxy !== '' ? settings.proxy : (systemProxy() ?? '')
|
|
194
194
|
if (proxy !== '') {
|
|
195
195
|
env.HTTP_PROXY = proxy
|
|
@@ -488,11 +488,14 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
488
488
|
// 空 body / 非 JSON:视为全量探测
|
|
489
489
|
}
|
|
490
490
|
const settings = loadSettings(profile)
|
|
491
|
+
// npm 通道:设置里配置了镜像源就探测该镜像(安装通道吃同一个 registry);
|
|
492
|
+
// 未配置(空串)时探测官方源作为「跟随本机 npm 配置」的基础连通性参考。
|
|
491
493
|
const registry = settings.npmRegistry.replace(/\/+$/, '') || 'https://registry.npmjs.org'
|
|
492
494
|
// 连通性自检直接打安装通道真实访问的轻量资源:npm 源拉包元数据、git 通道探
|
|
493
495
|
// 仓库主页、目录站探 badge 小文件,避免拉取目录全量 JSON。
|
|
496
|
+
// npm 行 display:配置了镜像就显示镜像地址;未配置置空 —— 客户端显示「未配置(跟随本机 npm 配置)」。
|
|
494
497
|
const allChecks: Array<{ key: string; url: string; display: string; cmd: string }> = [
|
|
495
|
-
{ key: 'npm', url: `${registry}/dsh-plugin`, display: settings.npmRegistry !== '' ?
|
|
498
|
+
{ key: 'npm', url: `${registry}/dsh-plugin`, display: settings.npmRegistry !== '' ? registry : '', cmd: `npm view dsh-plugin version --registry ${registry}` },
|
|
496
499
|
{ key: 'github', url: 'https://github.com/dshplugin/dsh-plugin-hub', display: 'github.com', cmd: 'git ls-remote https://github.com/dshplugin/dsh-plugin-hub' },
|
|
497
500
|
{ key: 'catalog', url: 'https://api.dsh-plugin.org/stats.json', display: 'api.dsh-plugin.org', cmd: 'curl -s https://api.dsh-plugin.org/stats.json' },
|
|
498
501
|
]
|
|
@@ -725,7 +728,9 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
725
728
|
}
|
|
726
729
|
// Kick off the CLI in the background; the client polls /status for progress
|
|
727
730
|
const task = startPluginMutation({
|
|
728
|
-
|
|
731
|
+
// mode=update(已安装目标的覆盖更新)以 update 动作入队:
|
|
732
|
+
// 日志按 update 类别落盘(update.start/done/fail),实际 CLI 执行仍是 add <target>@latest
|
|
733
|
+
action: mode === 'update' ? 'update' : 'add',
|
|
729
734
|
profile,
|
|
730
735
|
target,
|
|
731
736
|
// npm 安装时 target 是包名:displayTarget 固定展示仓库名,待重启行/前端恢复不受通道变化影响
|
|
@@ -27,7 +27,7 @@ export interface InstallTask {
|
|
|
27
27
|
attempts: string[]
|
|
28
28
|
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时任务执行全局 npm 安装,不进任何 profile */
|
|
29
29
|
globalNpm?: string[]
|
|
30
|
-
action: 'add' | 'remove'
|
|
30
|
+
action: 'add' | 'remove' | 'update'
|
|
31
31
|
/** 队列语义:pending 排队中 / running 执行中 / done / failed / cancelled(用户取消) */
|
|
32
32
|
status: 'pending' | 'running' | 'done' | 'failed' | 'cancelled'
|
|
33
33
|
timedOut: boolean
|
|
@@ -51,7 +51,7 @@ export interface Invocation {
|
|
|
51
51
|
/** 排队中的变更任务及其启动参数。 */
|
|
52
52
|
export interface QueueItem {
|
|
53
53
|
task: InstallTask
|
|
54
|
-
options: { action: 'add' | 'remove'; profile: string; target: string; timeoutMs?: number; env?: NodeJS.ProcessEnv; globalNpm?: string[] }
|
|
54
|
+
options: { action: 'add' | 'remove' | 'update'; profile: string; target: string; timeoutMs?: number; env?: NodeJS.ProcessEnv; globalNpm?: string[] }
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -23,7 +23,9 @@ const REQUEST_TIMEOUT_MS = 8000
|
|
|
23
23
|
* 反查 repo(`owner/repo`)对应的官方 npm 包名;未命中、网络异常或超时返回 null。
|
|
24
24
|
* 返回 null 不代表仓库一定没有 npm 包,只代表本次未能确认 —— 调用方应保留
|
|
25
25
|
* 原有错误路径,反查只是额外的一次尝试。
|
|
26
|
-
*
|
|
26
|
+
* registry 参数:npm 镜像源地址,空串 = 官方源;与安装通道吃同一 registry,
|
|
27
|
+
* 保证「配置了镜像」时反查和安装走同一个源(镜像节点同步完整时结果一致)。
|
|
28
|
+
* 慢网络下失败不阻塞安装。
|
|
27
29
|
*/
|
|
28
30
|
export function resolveNpmPackage(repo: string, registry = ''): Promise<string | null> {
|
|
29
31
|
const key = repo.toLowerCase()
|
|
@@ -38,7 +38,7 @@ export function getTask(id: number): InstallTask | undefined {
|
|
|
38
38
|
export interface ActiveTaskInfo {
|
|
39
39
|
id: number
|
|
40
40
|
target: string
|
|
41
|
-
action: 'add' | 'remove'
|
|
41
|
+
action: 'add' | 'remove' | 'update'
|
|
42
42
|
status: 'pending' | 'running'
|
|
43
43
|
progress: number
|
|
44
44
|
lines: string[]
|
|
@@ -158,13 +158,23 @@ function stopChild(child: ReturnType<typeof spawn>): void {
|
|
|
158
158
|
child.kill('SIGKILL')
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/** 系统日志类别:安装/卸载/更新三分,针对性地判断「哪一步不兼容」。 */
|
|
162
|
+
function mutationCategory(action: 'add' | 'remove' | 'update'): 'install' | 'uninstall' | 'update' {
|
|
163
|
+
return action === 'remove' ? 'uninstall' : action === 'update' ? 'update' : 'install'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 系统日志动作词:开始安装/开始卸载/开始更新。 */
|
|
167
|
+
function mutationVerb(action: 'add' | 'remove' | 'update'): string {
|
|
168
|
+
return action === 'remove' ? '开始卸载' : action === 'update' ? '开始更新' : '开始安装'
|
|
169
|
+
}
|
|
170
|
+
|
|
161
171
|
/**
|
|
162
172
|
* Enqueue a plugin mutation and return its task. Tasks run strictly serially:
|
|
163
173
|
* the queue worker starts the next one only after the previous finishes.
|
|
164
174
|
* Progress is visible through `getTask(id)` / `activeTask()` until done.
|
|
165
175
|
*/
|
|
166
176
|
export function startPluginMutation(options: {
|
|
167
|
-
action: 'add' | 'remove'
|
|
177
|
+
action: 'add' | 'remove' | 'update'
|
|
168
178
|
profile: string
|
|
169
179
|
target: string
|
|
170
180
|
timeoutMs?: number
|
|
@@ -193,14 +203,14 @@ export function startPluginMutation(options: {
|
|
|
193
203
|
})
|
|
194
204
|
}
|
|
195
205
|
queue.push({ task, options: { ...options } })
|
|
196
|
-
//
|
|
197
|
-
const category = options.action
|
|
206
|
+
// 系统日志:记录任务入队(安装/卸载/更新开始)
|
|
207
|
+
const category = mutationCategory(options.action)
|
|
198
208
|
appendLog(options.profile, {
|
|
199
209
|
at: Date.now(),
|
|
200
210
|
level: 'info',
|
|
201
211
|
category,
|
|
202
212
|
event: `${category}.start`,
|
|
203
|
-
message: `${options.action
|
|
213
|
+
message: `${mutationVerb(options.action)} ${options.displayTarget ?? options.target}`,
|
|
204
214
|
})
|
|
205
215
|
pumpQueue()
|
|
206
216
|
return task
|
|
@@ -215,16 +225,17 @@ function pumpQueue(): void {
|
|
|
215
225
|
task.status = 'running'
|
|
216
226
|
void runPluginMutation({ ...options, task }).finally(() => {
|
|
217
227
|
runningChildren.delete(task.id)
|
|
218
|
-
//
|
|
228
|
+
// 系统日志:任务终态(成功/失败/取消),按动作归类安装/卸载/更新日志
|
|
219
229
|
const show = task.displayTarget ?? task.target
|
|
220
|
-
const category = task.action
|
|
230
|
+
const category = mutationCategory(task.action)
|
|
231
|
+
const verb = (v: '成功' | '失败') => task.action === 'remove' ? `卸载${v}` : task.action === 'update' ? `更新${v}` : `安装${v}`
|
|
221
232
|
if (task.status === 'done') {
|
|
222
233
|
appendLog(options.profile, {
|
|
223
234
|
at: Date.now(),
|
|
224
235
|
level: 'success',
|
|
225
236
|
category,
|
|
226
237
|
event: `${category}.done`,
|
|
227
|
-
message: `${
|
|
238
|
+
message: `${verb('成功')} ${show}${task.needsRestart ? '(待重启生效)' : ''}`,
|
|
228
239
|
})
|
|
229
240
|
} else if (task.status === 'failed') {
|
|
230
241
|
appendLog(options.profile, {
|
|
@@ -232,7 +243,7 @@ function pumpQueue(): void {
|
|
|
232
243
|
level: 'error',
|
|
233
244
|
category,
|
|
234
245
|
event: `${category}.fail`,
|
|
235
|
-
message: `${
|
|
246
|
+
message: `${verb('失败')} ${show}`,
|
|
236
247
|
})
|
|
237
248
|
} else if (task.status === 'cancelled') {
|
|
238
249
|
appendLog(options.profile, {
|
|
@@ -256,7 +267,7 @@ function pumpQueue(): void {
|
|
|
256
267
|
* Never rejects; failures surface through the result / task state.
|
|
257
268
|
*/
|
|
258
269
|
function spawnMutation(options: {
|
|
259
|
-
action: 'add' | 'remove'
|
|
270
|
+
action: 'add' | 'remove' | 'update'
|
|
260
271
|
profile: string
|
|
261
272
|
target: string
|
|
262
273
|
timeoutMs?: number
|
|
@@ -280,17 +291,21 @@ function spawnMutation(options: {
|
|
|
280
291
|
// 更新已安装的 npm 包:显式指定 @latest。pnpm add <pkg>(无版本)对已存在依赖
|
|
281
292
|
// (尤其精确 spec 如 "1.3.0")是幂等的 —— 输出 "Already up to date" 且不改 package.json,
|
|
282
293
|
// 导致「更新成功」但实际仍停留在旧版本;git 地址与全局安装不受此影响。
|
|
283
|
-
|
|
294
|
+
// update 动作实际执行 `add <target>@latest`(pnpm 对已存在依赖带显式版本即原位覆盖升级),
|
|
295
|
+
// CLI 动词仍透传 add —— 不执行官方 update 动词,避免与既有覆盖重装机制分叉。
|
|
296
|
+
const isUpdate = action === 'update' || updateNpm === true
|
|
297
|
+
const cliAction = action === 'update' ? 'add' : action
|
|
298
|
+
const npmUpdateTarget = isUpdate && !isGlobalNpm && githubRepoOf(target) === null
|
|
284
299
|
? `${target}@latest`
|
|
285
300
|
: target
|
|
286
301
|
const args = isGlobalNpm
|
|
287
302
|
? ['install', '-g', ...(globalNpm ?? [])]
|
|
288
|
-
: [...invocation.prefixArgs, 'plugin', '--profile', profile,
|
|
303
|
+
: [...invocation.prefixArgs, 'plugin', '--profile', profile, cliAction, npmUpdateTarget]
|
|
289
304
|
// 记录实际执行的命令(npm 通道显示包名,git 通道显示 HTTPS 源),
|
|
290
305
|
// 失败提 Issue 时作为「已尝试的安装方式」随附
|
|
291
306
|
const executed = isGlobalNpm
|
|
292
307
|
? `npm install -g ${(globalNpm ?? []).join(' ')}`
|
|
293
|
-
: `dsh plugin --profile ${profile} ${
|
|
308
|
+
: `dsh plugin --profile ${profile} ${cliAction} ${npmUpdateTarget}`
|
|
294
309
|
if (task && !task.attempts.includes(executed)) {
|
|
295
310
|
task.attempts.push(executed)
|
|
296
311
|
}
|
|
@@ -443,10 +458,10 @@ function spawnMutation(options: {
|
|
|
443
458
|
if (removed) clearPendingRestart(displayTarget ?? target)
|
|
444
459
|
else addPendingRestart(displayTarget ?? target, 'uninstall')
|
|
445
460
|
} else {
|
|
446
|
-
//
|
|
461
|
+
// 安装/更新成功(或卸载但无 loader 引用)→ 登记待重启:
|
|
447
462
|
// 插件要宿主重启才会挂载/卸载干净,重启前一直提醒;展示目标用 displayTarget(owner/repo)
|
|
448
463
|
task.needsRestart = true
|
|
449
|
-
addPendingRestart(displayTarget ?? target, action === '
|
|
464
|
+
addPendingRestart(displayTarget ?? target, action === 'remove' ? 'uninstall' : 'install')
|
|
450
465
|
}
|
|
451
466
|
}
|
|
452
467
|
pushLine(task, result.timedOut ? '[timed out]' : `[exit ${result.exitCode ?? '?'}]`)
|
|
@@ -513,11 +528,11 @@ function ignoredBuildKeys(output: string): string[] {
|
|
|
513
528
|
* 前端据此展示「本机 npm 版本过低」的准确原因 —— 避免该报错被外层 ERR_PNPM_PREPARE_PACKAGE
|
|
514
529
|
* 误判成插件打包/分发问题、误导用户提 Issue。检测逻辑在独立的 npm-check.ts(本机 npm 环境职责)。 */
|
|
515
530
|
function annotateNpmTooLow(result: InstallResult, options: {
|
|
516
|
-
action: 'add' | 'remove'
|
|
531
|
+
action: 'add' | 'remove' | 'update'
|
|
517
532
|
task?: InstallTask
|
|
518
533
|
env?: NodeJS.ProcessEnv
|
|
519
534
|
}): InstallResult {
|
|
520
|
-
if (options.action
|
|
535
|
+
if (options.action === 'remove' || result.exitCode === 0 || result.timedOut) return result
|
|
521
536
|
const line = npmTooLowMarker(`${result.stderr}\n${result.stdout}`, options.env)
|
|
522
537
|
if (line === null) return result
|
|
523
538
|
if (options.task && options.task.status !== 'cancelled') pushLine(options.task, line)
|
|
@@ -534,7 +549,7 @@ function annotateNpmTooLow(result: InstallResult, options: {
|
|
|
534
549
|
* Other failures fall through to the single-spawn result.
|
|
535
550
|
*/
|
|
536
551
|
export async function runPluginMutation(options: {
|
|
537
|
-
action: 'add' | 'remove'
|
|
552
|
+
action: 'add' | 'remove' | 'update'
|
|
538
553
|
profile: string
|
|
539
554
|
target: string
|
|
540
555
|
timeoutMs?: number
|
|
@@ -550,7 +565,8 @@ export async function runPluginMutation(options: {
|
|
|
550
565
|
let result = await spawnMutation(options)
|
|
551
566
|
// allowBuilds 放行 / 装后入口校验都是 profile 专属(pnpm 装进 profile 的场景);
|
|
552
567
|
// 全局 npm 安装直接走 npm,无 pnpm allowBuilds 拦截、也不进 profile,跳过这两类恢复
|
|
553
|
-
|
|
568
|
+
// 安装与更新(同为 add 通道执行)共享同一套恢复路径;卸载不适用
|
|
569
|
+
if (options.action !== 'remove' && result.exitCode !== 0 && !result.timedOut && (options.globalNpm ?? []).length === 0) {
|
|
554
570
|
const output = `${result.stderr}\n${result.stdout}`
|
|
555
571
|
// git 源:pnpm 打印的 key@url 提示
|
|
556
572
|
if (output.includes('ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED')) {
|
|
@@ -588,7 +604,7 @@ export async function runPluginMutation(options: {
|
|
|
588
604
|
// pnpm 安装本身成功,但目标包入口文件缺失(git 分发缺构建产物)→ 判为失败:
|
|
589
605
|
// 撤销刚登记的待重启,避免用户重启时宿主加载残缺包直接崩溃。
|
|
590
606
|
// 全局 npm 安装不进 profile(profile deps 里没有这些包),跳过入口校验
|
|
591
|
-
if (options.action
|
|
607
|
+
if (options.action !== 'remove' && result.exitCode === 0 && !result.timedOut && (options.globalNpm ?? []).length === 0) {
|
|
592
608
|
const verified = verifyInstalledEntry(options.profile, options.target)
|
|
593
609
|
if (verified.name !== null && verified.missing !== null) {
|
|
594
610
|
clearPendingRestart(options.displayTarget ?? options.target)
|
|
@@ -30,17 +30,18 @@ export function githubTarget(repo: string): string | null {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> add <target>`)—— 提取其中的安装目标;
|
|
33
|
+
* DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> <add|update> <target>`)—— 提取其中的安装目标;
|
|
34
34
|
* 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 必填且只有长选项
|
|
35
35
|
* (官方 CLI 无 -p 简写,省略即报错),支持 `--profile=<name>` 等号形式,命令大小写不敏感。
|
|
36
|
+
* update = 更新已安装目标到最新;remove 不在此列(卸载走 /uninstall 路由,按已装包名直卸)。
|
|
36
37
|
* 与客户端 CustomInstallView 口径一致。
|
|
37
38
|
*/
|
|
38
|
-
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+add\s+(.+)$/i
|
|
39
|
+
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+(add|update)\s+(.+)$/i
|
|
39
40
|
|
|
40
41
|
export function installTargetOf(value: string): string {
|
|
41
42
|
const input = typeof value === 'string' ? value.trim() : ''
|
|
42
43
|
const match = DSH_PLUGIN_CMD_RE.exec(input)
|
|
43
|
-
return match !== null ? match[
|
|
44
|
+
return match !== null ? match[2].trim() : input
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|