dsh-plugin 1.1.2 → 1.2.0
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 +4469 -1445
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +438 -22
- package/lib/index.js +1 -1
- package/lib/services/install/install.js +4 -0
- package/lib/services/install/npm-check.js +55 -0
- package/lib/services/{npm-resolve.js → install/npm-resolve.js} +7 -5
- package/lib/services/{preflight.js → install/preflight.js} +15 -4
- package/lib/services/{task-queue.js → install/task-queue.js} +155 -66
- package/lib/services/loader.js +56 -1
- package/lib/services/log.js +138 -0
- package/lib/services/probe.js +157 -0
- package/lib/services/{profile.js → profile/profile.js} +29 -1
- package/lib/services/settings.js +60 -0
- package/lib/types/http/routes.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +3 -0
- package/lib/types/services/{install.d.ts → install/install.d.ts} +5 -5
- package/lib/types/services/install/npm-check.d.ts +12 -0
- package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
- package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
- package/lib/types/services/loader.d.ts +14 -8
- package/lib/types/services/log.d.ts +49 -0
- package/lib/types/services/probe.d.ts +30 -0
- package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +1 -1
- package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +2 -0
- package/lib/types/services/settings.d.ts +24 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +341 -120
- package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +11 -7
- package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +5 -4
- package/src/client/components/layout/CatalogControls.tsx +80 -0
- package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +4 -4
- package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +3 -3
- package/src/client/components/layout/SectionTabs.tsx +60 -0
- package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +7 -7
- package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +5 -5
- package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
- package/src/client/components/modals/LogsModal.tsx +301 -0
- package/src/client/components/modals/LogsPathModal.tsx +161 -0
- package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +47 -20
- package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +2 -2
- package/src/client/components/{modals.tsx → modals/modals.tsx} +117 -40
- package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +14 -6
- package/src/client/components/ui/Toggle.tsx +28 -0
- package/src/client/components/ui/icons.tsx +343 -0
- package/src/client/components/views/CustomInstallView.tsx +250 -0
- package/src/client/components/views/DiagnosticsView.tsx +172 -0
- package/src/client/components/views/InstalledView.tsx +328 -0
- package/src/client/components/views/LogsView.tsx +150 -0
- package/src/client/components/views/MarketView.tsx +66 -0
- package/src/client/components/views/SettingsView.tsx +273 -0
- package/src/client/data/catalog.ts +38 -0
- package/src/client/data/host.ts +107 -0
- package/src/client/data/hub.ts +41 -0
- package/src/client/hooks/useCatalog.ts +71 -115
- package/src/client/hooks/useIncrementalList.ts +46 -0
- package/src/client/hooks/useLanguage.ts +40 -0
- package/src/client/hooks/useSettings.ts +90 -0
- package/src/client/hooks/useTaskQueue.ts +156 -39
- package/src/client/locales.ts +358 -38
- package/src/client/logic/constants.ts +87 -0
- package/src/client/{lib → logic}/failures.ts +95 -74
- package/src/client/logic/install-command.ts +56 -0
- package/src/client/logic/installed.ts +207 -0
- package/src/client/logic/normalize.ts +52 -0
- package/src/client/logic/urls.ts +125 -0
- package/src/client/styles/CustomInstallView.module.css +191 -0
- package/src/client/styles/DiagnosticsView.module.css +231 -0
- package/src/client/styles/Dropdown.module.css +42 -41
- package/src/client/styles/Header.module.css +100 -132
- package/src/client/styles/InstalledView.module.css +447 -0
- package/src/client/styles/List.module.css +6 -0
- package/src/client/styles/LogsView.module.css +503 -0
- package/src/client/styles/Modal.module.css +352 -70
- package/src/client/styles/SectionTabs.module.css +142 -0
- package/src/client/styles/SettingsView.module.css +232 -0
- package/src/client/styles/Toggle.module.css +60 -0
- package/src/client/styles/tokens.module.css +1 -0
- package/src/client/types.ts +23 -3
- package/src/server/http/routes.ts +414 -22
- package/src/server/index.ts +1 -1
- package/src/server/services/{install-types.ts → install/install-types.ts} +3 -1
- package/src/server/services/{install.ts → install/install.ts} +5 -5
- package/src/server/services/install/npm-check.ts +53 -0
- package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +7 -5
- package/src/server/services/{preflight.ts → install/preflight.ts} +16 -4
- package/src/server/services/{task-queue.ts → install/task-queue.ts} +157 -63
- package/src/server/services/loader.ts +55 -1
- package/src/server/services/log.ts +167 -0
- package/src/server/services/probe.ts +157 -0
- package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +1 -1
- package/src/server/services/{profile.ts → profile/profile.ts} +31 -1
- package/src/server/services/settings.ts +79 -0
- package/lib/services/install.js +0 -4
- package/src/client/components/CatalogControls.tsx +0 -108
- package/src/client/components/icons.tsx +0 -129
- package/src/client/lib/catalog.ts +0 -293
- package/src/client/lib/env.ts +0 -20
- /package/lib/services/{install-types.js → install/install-types.js} +0 -0
- /package/lib/services/{installed-versions.js → profile/installed-versions.js} +0 -0
- /package/lib/services/{pending-restart.js → profile/pending-restart.js} +0 -0
- /package/lib/services/{progress.js → profile/progress.js} +0 -0
- /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
- /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
- /package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +0 -0
- /package/src/client/{lib → logic}/format.ts +0 -0
- /package/src/client/{lib → logic}/renderMarkdown.ts +0 -0
- /package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +0 -0
- /package/src/server/services/{progress.ts → profile/progress.ts} +0 -0
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { useEffect, useRef, useState } from 'react'
|
|
13
13
|
import type { HubPlugin, Translate } from '../types.ts'
|
|
14
|
-
import { installCommandOf, installTargetOf, repoFromInstallTarget } from '../
|
|
14
|
+
import { installCommandOf, installTargetOf, repoFromInstallTarget } from '../logic/install-command.ts'
|
|
15
|
+
import type { InstalledItem } from '../logic/installed.ts'
|
|
16
|
+
|
|
17
|
+
/** 安装/卸载请求超时(ms):服务端 preflight + npm 反查可能耗时(各自都有超时,合计约 20~30s),
|
|
18
|
+
* 给足余量;超时主动中止,避免服务端 preflight 网络挂起时前端永久卡「安装中 0%」。 */
|
|
19
|
+
const REQUEST_TIMEOUT_MS = 60_000
|
|
15
20
|
|
|
16
21
|
/** 客户端任务队列项:镜像服务端 active 任务(running 在前、pending 在后)。 */
|
|
17
22
|
export interface QueueTask {
|
|
@@ -19,7 +24,7 @@ export interface QueueTask {
|
|
|
19
24
|
kind: 'install' | 'uninstall'
|
|
20
25
|
/** 展示目标:owner/repo(安装)或 npm 包名(卸载) */
|
|
21
26
|
target: string
|
|
22
|
-
/**
|
|
27
|
+
/** 插件中文简介(入队时从插件数据带过来,轮询合并时保留):展示在仓库名下方,标识排队中的插件 */
|
|
23
28
|
desc?: string
|
|
24
29
|
/** 所属插件仓库(owner/repo):失败时错误弹窗据此提供「去仓库反馈」入口;卸载为 null */
|
|
25
30
|
repo: string | null
|
|
@@ -65,7 +70,14 @@ export interface TaskQueueOptions {
|
|
|
65
70
|
onError: (message: string, repo: string | null, kind: 'install' | 'uninstall', command?: string, attempts?: string[]) => void
|
|
66
71
|
/** 当前打开的安装/卸载弹窗插件:用于在弹窗内匹配进行中的任务。 */
|
|
67
72
|
installPlugin: HubPlugin | null
|
|
73
|
+
/** 当前打开的自定义安装(命令行)确认弹窗目标:与应用商店同一弹窗机制,匹配进行中的 custom 安装任务。 */
|
|
74
|
+
installCustomTarget?: string | null
|
|
75
|
+
/** 当前打开的全局 npm 安装(`npm install -g <pkgs>`)确认弹窗身份(包列表空格拼接):
|
|
76
|
+
* 匹配服务端登记的全局安装任务(target = 包列表 join(' '))。 */
|
|
77
|
+
installGlobalTarget?: string | null
|
|
68
78
|
uninstallPlugin: HubPlugin | null
|
|
79
|
+
/** 已安装视图发起的卸载目标(npm 包名):目录插件走 uninstallPlugin,自定义安装走包名直卸。 */
|
|
80
|
+
uninstallName: string | null
|
|
69
81
|
installedName: (p: HubPlugin) => string | null
|
|
70
82
|
/** 待重启项展示信息补齐:按 owner/repo 从插件目录解析简介/版本;找不到返回 null。 */
|
|
71
83
|
resolvePending: (repo: string) => { desc?: string; version?: string } | null
|
|
@@ -74,7 +86,7 @@ export interface TaskQueueOptions {
|
|
|
74
86
|
export function useTaskQueue(opts: TaskQueueOptions) {
|
|
75
87
|
const {
|
|
76
88
|
t, refreshInstalled, onInstallDone, onUninstallDone, onError,
|
|
77
|
-
installPlugin, uninstallPlugin, installedName, resolvePending,
|
|
89
|
+
installPlugin, installCustomTarget, installGlobalTarget, uninstallPlugin, uninstallName, installedName, resolvePending,
|
|
78
90
|
} = opts
|
|
79
91
|
const [queue, setQueue] = useState<QueueTask[]>([])
|
|
80
92
|
const queueRef = useRef<QueueTask[]>([])
|
|
@@ -374,46 +386,67 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
374
386
|
}, [])
|
|
375
387
|
|
|
376
388
|
/** 弹窗动作:直接安装。请求宿主本地路由,任务进入服务端队列(FIFO),弹窗内实时显示进度。 */
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
389
|
+
/** 安装入队核心:目录插件(catalog,后端走目录白名单)与命令行安装(custom,
|
|
390
|
+
* 受安全开关控制)共用。队列条目一律用仓库名(repo)展示/防重,只有发给后端的
|
|
391
|
+
* 实际安装目标(target)随通道变化(npm 包名 / github: 源)。 */
|
|
392
|
+
const submitInstall = async (input: {
|
|
393
|
+
/** 发给后端的实际安装目标:npm 包名或 github: 源(服务端再决定最终通道) */
|
|
394
|
+
target: string
|
|
395
|
+
/** 队列展示与防重用的仓库名 */
|
|
396
|
+
repo: string
|
|
397
|
+
desc?: string
|
|
398
|
+
version?: string
|
|
399
|
+
updatedAt?: string
|
|
400
|
+
command: string
|
|
401
|
+
source: 'catalog' | 'custom'
|
|
402
|
+
/** 更新已安装目标(放行 add 覆盖重装) */
|
|
403
|
+
update?: boolean
|
|
404
|
+
/** 全局 npm 安装包列表(`npm install -g <pkgs>`):非空时服务端执行全局安装,不进任何 profile */
|
|
405
|
+
globalNpm?: string[]
|
|
406
|
+
}) => {
|
|
384
407
|
// 防重复入队:同一目标已在排队/执行中则忽略
|
|
385
|
-
if (queueRef.current.some((q) => q.kind === 'install' && q.target === repo)) return
|
|
408
|
+
if (queueRef.current.some((q) => q.kind === 'install' && q.target === input.repo)) return
|
|
386
409
|
// 请求在途防重:任务要等响应回来才进本地队列,这之前再次点击(双击)直接忽略
|
|
387
|
-
if (submittingRef.current.has(repo)) return
|
|
388
|
-
submittingRef.current.add(repo)
|
|
410
|
+
if (submittingRef.current.has(input.repo)) return
|
|
411
|
+
submittingRef.current.add(input.repo)
|
|
389
412
|
// 乐观入队:点击安装立即显示「正在安装中」,不等 POST 响应。
|
|
390
413
|
// 服务端 /install 会先跑预检(preflight,可能耗时数秒)再入队,若等响应才入队列,
|
|
391
|
-
//
|
|
414
|
+
// 用户点完安装立刻去看任务会看不到 —— 只有刷新后 restore 才拉回来。
|
|
392
415
|
// 用负临时 id 占位,响应回来换成真实 task id;失败/出错则移除该占位。
|
|
393
416
|
tempIdRef.current -= 1
|
|
394
417
|
const tempId = tempIdRef.current
|
|
395
418
|
modalTaskRef.current = tempId
|
|
396
419
|
applyQueue((prev) => [...prev, {
|
|
397
|
-
id: tempId, kind: 'install', target: repo, repo, desc:
|
|
398
|
-
version:
|
|
420
|
+
id: tempId, kind: 'install', target: input.repo, repo: input.repo, desc: input.desc,
|
|
421
|
+
version: input.version, updatedAt: input.updatedAt,
|
|
399
422
|
status: 'running', progress: 0, lines: [], optimistic: true,
|
|
400
|
-
command:
|
|
423
|
+
command: input.command,
|
|
401
424
|
// 服务端登记后 /active 会带回真实尝试记录(npm 反查 + 执行命令),先占位空列表
|
|
402
425
|
attempts: [], needsRestart: true,
|
|
403
426
|
}])
|
|
404
427
|
setSubmitting(true)
|
|
428
|
+
const controller = new AbortController()
|
|
429
|
+
const timeout = window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)
|
|
405
430
|
try {
|
|
406
431
|
const res = await fetch('/dsh-plugin-hub/install', {
|
|
407
432
|
method: 'POST',
|
|
408
433
|
headers: { 'content-type': 'application/json' },
|
|
409
|
-
body: JSON.stringify({
|
|
434
|
+
body: JSON.stringify({
|
|
435
|
+
repo: input.target,
|
|
436
|
+
display: input.repo,
|
|
437
|
+
source: input.source,
|
|
438
|
+
mode: input.update ? 'update' : undefined,
|
|
439
|
+
...(input.globalNpm && input.globalNpm.length > 0 ? { globalNpm: input.globalNpm } : {}),
|
|
440
|
+
}),
|
|
441
|
+
signal: controller.signal,
|
|
410
442
|
})
|
|
411
443
|
const data = await res.json() as { ok?: boolean; task?: number; error?: string; attempts?: string[] }
|
|
412
444
|
if (!data.ok || typeof data.task !== 'number') {
|
|
413
|
-
// 请求层失败(重复安装 409 /
|
|
445
|
+
// 请求层失败(重复安装 409 / 参数错误 / 安全开关 403 等):移除乐观条目 + 完整错误弹窗
|
|
414
446
|
applyQueue((prev) => prev.filter((x) => x.id !== tempId))
|
|
415
|
-
// 同步 400(如预检拦截)服务端会附上已尝试的安装方式,issue
|
|
416
|
-
|
|
447
|
+
// 同步 400(如预检拦截)服务端会附上已尝试的安装方式,issue 预填一并展示;
|
|
448
|
+
// 用 || 而非 ?? :error 为空字符串时回退 HTTP 状态,避免复制出来是空白
|
|
449
|
+
onError(data.error || `HTTP ${res.status}`, input.repo, 'install', input.command, data.attempts)
|
|
417
450
|
return
|
|
418
451
|
}
|
|
419
452
|
const taskId = data.task
|
|
@@ -423,53 +456,129 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
423
456
|
? { ...x, id: taskId, optimistic: undefined }
|
|
424
457
|
: x)))
|
|
425
458
|
// 提前缓存展示信息:安装成功后服务端登记待重启,轮询合并时不用等目录解析
|
|
426
|
-
pendingInfoRef.current.set(repo, { desc:
|
|
459
|
+
pendingInfoRef.current.set(input.repo, { desc: input.desc, version: input.version })
|
|
427
460
|
// 摘要条常驻顶部(含实时进度),明细面板保持折叠,想看时再点开
|
|
428
461
|
pollQueue()
|
|
429
|
-
} catch {
|
|
430
|
-
// 网络异常:移除乐观条目 +
|
|
462
|
+
} catch (err) {
|
|
463
|
+
// 网络异常:移除乐观条目 + 兜底错误弹窗(附上底层原因,避免只剩一句笼统的「安装失败」)
|
|
431
464
|
applyQueue((prev) => prev.filter((x) => x.id !== tempId))
|
|
432
|
-
|
|
465
|
+
const aborted = (err as { name?: string } | null)?.name === 'AbortError'
|
|
466
|
+
const reason = aborted
|
|
467
|
+
? t('requestTimeout')
|
|
468
|
+
: err instanceof Error && err.message ? err.message : String(err ?? '')
|
|
469
|
+
onError(reason ? `${t('installFail')} — ${reason}` : t('installFail'), input.repo, 'install', input.command)
|
|
433
470
|
} finally {
|
|
434
|
-
|
|
471
|
+
window.clearTimeout(timeout)
|
|
472
|
+
submittingRef.current.delete(input.repo)
|
|
435
473
|
setSubmitting(false)
|
|
436
474
|
}
|
|
437
475
|
}
|
|
438
476
|
|
|
439
|
-
/**
|
|
440
|
-
const
|
|
441
|
-
const
|
|
477
|
+
/** 目录插件安装:通道由目录数据(npmPackage)决定,走 catalog 白名单。 */
|
|
478
|
+
const installNow = async (p: HubPlugin, opts?: { update?: boolean }) => {
|
|
479
|
+
const repo = p.source?.repo ?? ''
|
|
480
|
+
if (!repo) return
|
|
481
|
+
// 安装通道决策(用户无感知):有 npm 包名 → 走 npm;否则 git 直装。
|
|
482
|
+
const { target } = installTargetOf(p)
|
|
483
|
+
if (!target) return
|
|
484
|
+
await submitInstall({
|
|
485
|
+
target,
|
|
486
|
+
repo,
|
|
487
|
+
desc: p.description,
|
|
488
|
+
version: p.version,
|
|
489
|
+
updatedAt: p.dates?.repoUpdatedAt,
|
|
490
|
+
command: installCommandOf(p, true),
|
|
491
|
+
source: 'catalog',
|
|
492
|
+
update: opts?.update,
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** 命令行安装:用户手输 npm 包名或 GitHub 地址,走 custom 源(受安全开关控制)。
|
|
497
|
+
* update=true 时对已安装目标放行覆盖重装(与目录插件「更新」同一语义)。
|
|
498
|
+
* 队列展示/防重一律用归一化身份(owner/repo 或 npm 包名):与服务端 /active 合并、
|
|
499
|
+
* 弹窗目标匹配同口径,npm 包名透传不受影响。 */
|
|
500
|
+
const installCustom = async (raw: string, opts?: { update?: boolean }) => {
|
|
501
|
+
const target = raw.trim()
|
|
502
|
+
if (!target) return
|
|
503
|
+
const repo = repoFromInstallTarget(target)
|
|
504
|
+
await submitInstall({
|
|
505
|
+
target,
|
|
506
|
+
repo,
|
|
507
|
+
command: `pnpm add ${target}`,
|
|
508
|
+
source: 'custom',
|
|
509
|
+
update: opts?.update,
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** 全局 npm 安装(官方 README 的 `npm install -g <pkgs>`):不进任何 profile、无需宿主重启。
|
|
514
|
+
* 队列展示/防重用包列表空格拼接的身份(与服务端 target 同口径)。 */
|
|
515
|
+
const installGlobalNpm = async (pkgs: string[], opts?: { update?: boolean }) => {
|
|
516
|
+
const list = (pkgs ?? []).map((p) => p.trim()).filter((p) => p !== '')
|
|
517
|
+
if (list.length === 0) return
|
|
518
|
+
const identity = list.join(' ')
|
|
519
|
+
await submitInstall({
|
|
520
|
+
target: identity,
|
|
521
|
+
repo: identity,
|
|
522
|
+
command: `npm install -g ${identity}`,
|
|
523
|
+
source: 'custom',
|
|
524
|
+
globalNpm: list,
|
|
525
|
+
update: opts?.update,
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** 卸载入队核心:按 npm 包名直卸(目录插件与自定义安装共用,弹窗进度匹配同源)。 */
|
|
530
|
+
const enqueueUninstall = async (name: string, repo: string | null, desc?: string) => {
|
|
442
531
|
if (!name) return
|
|
443
|
-
const repo = p.source?.repo ?? null
|
|
444
532
|
if (queueRef.current.some((q) => q.kind === 'uninstall' && q.target === name)) return
|
|
445
533
|
// 请求在途防重:与安装一致,响应回来前再次点击直接忽略
|
|
446
534
|
if (submittingRef.current.has(name)) return
|
|
447
535
|
submittingRef.current.add(name)
|
|
448
536
|
setSubmitting(true)
|
|
537
|
+
const controller = new AbortController()
|
|
538
|
+
const timeout = window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)
|
|
449
539
|
try {
|
|
450
540
|
const res = await fetch('/dsh-plugin-hub/uninstall', {
|
|
451
541
|
method: 'POST',
|
|
452
542
|
headers: { 'content-type': 'application/json' },
|
|
453
543
|
body: JSON.stringify({ name, repo: repo ?? undefined }),
|
|
544
|
+
signal: controller.signal,
|
|
454
545
|
})
|
|
455
546
|
const data = await res.json() as { ok?: boolean; task?: number; error?: string }
|
|
456
547
|
if (!data.ok || typeof data.task !== 'number') {
|
|
457
|
-
onError(data.error
|
|
548
|
+
onError(data.error || `HTTP ${res.status}`, repo, 'uninstall')
|
|
458
549
|
return
|
|
459
550
|
}
|
|
460
551
|
const taskId = data.task
|
|
461
552
|
modalTaskRef.current = taskId
|
|
462
|
-
applyQueue((prev) => [...prev, { id: taskId, kind: 'uninstall', target: name, desc
|
|
553
|
+
applyQueue((prev) => [...prev, { id: taskId, kind: 'uninstall', target: name, desc, repo, status: 'pending', progress: 0, lines: [], needsRestart: true }])
|
|
463
554
|
// 摘要条常驻顶部(含实时进度),明细面板保持折叠,想看时再点开
|
|
464
555
|
pollQueue()
|
|
465
|
-
} catch {
|
|
466
|
-
|
|
556
|
+
} catch (err) {
|
|
557
|
+
// 网络异常:附上底层原因,避免只剩一句笼统的「卸载失败」
|
|
558
|
+
const aborted = (err as { name?: string } | null)?.name === 'AbortError'
|
|
559
|
+
const reason = aborted
|
|
560
|
+
? t('requestTimeout')
|
|
561
|
+
: err instanceof Error && err.message ? err.message : String(err ?? '')
|
|
562
|
+
onError(reason ? `${t('uninstallFail')} — ${reason}` : t('uninstallFail'), repo, 'uninstall')
|
|
467
563
|
} finally {
|
|
564
|
+
window.clearTimeout(timeout)
|
|
468
565
|
submittingRef.current.delete(name)
|
|
469
566
|
setSubmitting(false)
|
|
470
567
|
}
|
|
471
568
|
}
|
|
472
569
|
|
|
570
|
+
/** 弹窗动作:直接卸载(目录插件入口)。与安装同一队列机制,弹窗内实时显示进度。 */
|
|
571
|
+
const uninstallNow = async (p: HubPlugin) => {
|
|
572
|
+
const name = installedName(p)
|
|
573
|
+
if (!name) return
|
|
574
|
+
await enqueueUninstall(name, p.source?.repo ?? null, p.description)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** 已安装视图动作:按已安装项卸载(覆盖目录外自定义安装,无目录数据也能卸)。 */
|
|
578
|
+
const uninstallItem = async (item: InstalledItem) => {
|
|
579
|
+
await enqueueUninstall(item.name, item.repo, item.plugin?.description)
|
|
580
|
+
}
|
|
581
|
+
|
|
473
582
|
/** 取消任务:排队中立即出队,执行中终止子进程;先标记「正在取消」短暂过渡后再移除。 */
|
|
474
583
|
const cancelTask = async (id: number) => {
|
|
475
584
|
if (modalTaskRef.current === id) modalTaskRef.current = null
|
|
@@ -493,15 +602,20 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
493
602
|
const clearModalTask = () => { modalTaskRef.current = null }
|
|
494
603
|
|
|
495
604
|
// 弹窗对应任务:优先当前弹窗发起任务,其次按目标匹配(重新打开弹窗 / 目标已在队列时也能展示实时进度)
|
|
605
|
+
// 目录插件按 source.repo 匹配;命令行安装(custom)按归一化后的仓库身份/包名匹配;
|
|
606
|
+
// 全局 npm 安装按包列表身份(空格拼接)匹配;
|
|
496
607
|
// 排除 cancelling:取消中的任务不再关联到弹窗,弹窗回到「确认」态可重新操作
|
|
497
|
-
const
|
|
498
|
-
? (
|
|
499
|
-
|
|
608
|
+
const installModalTarget = installPlugin?.source?.repo
|
|
609
|
+
?? (installCustomTarget ? repoFromInstallTarget(installCustomTarget) : null)
|
|
610
|
+
?? installGlobalTarget ?? null
|
|
611
|
+
const installModalTask = (installPlugin || installCustomTarget || installGlobalTarget)
|
|
612
|
+
? (queue.find((q) => q.id === modalTaskRef.current && q.kind === 'install' && q.status !== 'cancelling')
|
|
613
|
+
?? queue.find((q) => q.kind === 'install' && q.target === installModalTarget && q.status !== 'cancelling')
|
|
500
614
|
?? null)
|
|
501
615
|
: null
|
|
502
|
-
const uninstallModalTask = uninstallPlugin
|
|
616
|
+
const uninstallModalTask = (uninstallPlugin || uninstallName)
|
|
503
617
|
? (queue.find((q) => q.id === modalTaskRef.current && q.status !== 'cancelling')
|
|
504
|
-
?? queue.find((q) => q.kind === 'uninstall' && q.target === (installedName(uninstallPlugin) ?? '') && q.status !== 'cancelling')
|
|
618
|
+
?? queue.find((q) => q.kind === 'uninstall' && q.target === (uninstallName ?? installedName(uninstallPlugin!) ?? '') && q.status !== 'cancelling')
|
|
505
619
|
?? null)
|
|
506
620
|
: null
|
|
507
621
|
|
|
@@ -512,7 +626,10 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
512
626
|
uninstallModalTask,
|
|
513
627
|
submitting,
|
|
514
628
|
installNow,
|
|
629
|
+
installCustom,
|
|
630
|
+
installGlobalNpm,
|
|
515
631
|
uninstallNow,
|
|
632
|
+
uninstallItem,
|
|
516
633
|
cancelTask,
|
|
517
634
|
clearModalTask,
|
|
518
635
|
}
|