dsh-plugin 1.6.12 → 2.0.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.
Files changed (128) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/client/client.js +615 -741
  4. package/client/client.js.map +1 -1
  5. package/lib/http/catalog.js +2 -3
  6. package/lib/http/diagnostics.js +6 -7
  7. package/lib/http/install.js +47 -213
  8. package/lib/http/logs.js +1 -2
  9. package/lib/http/npm-release-age.js +42 -0
  10. package/lib/http/routes.js +8 -7
  11. package/lib/http/settings.js +2 -2
  12. package/lib/http/shared.js +2 -2
  13. package/lib/http/task.js +1 -2
  14. package/lib/index.js +5 -6
  15. package/lib/services/install/install-types.js +4 -4
  16. package/lib/services/install/install.js +1 -1
  17. package/lib/services/install/npm-check.js +3 -2
  18. package/lib/services/install/npm-resolve.js +9 -96
  19. package/lib/services/install/package-entry.js +6 -11
  20. package/lib/services/install/registry-check.js +2 -2
  21. package/lib/services/install/release-target.js +8 -9
  22. package/lib/services/install/task-queue.js +40 -48
  23. package/lib/services/npm-release-age.js +119 -0
  24. package/lib/services/probe.js +4 -4
  25. package/lib/services/profile/installed-versions.js +15 -21
  26. package/lib/services/profile/profile.js +16 -44
  27. package/lib/services/profile/progress.js +8 -11
  28. package/lib/services/settings.js +2 -5
  29. package/lib/types/http/catalog.d.ts +1 -1
  30. package/lib/types/http/install.d.ts +2 -2
  31. package/lib/types/http/npm-release-age.d.ts +2 -0
  32. package/lib/types/http/routes.d.ts +10 -12
  33. package/lib/types/http/shared.d.ts +5 -6
  34. package/lib/types/index.d.ts +7 -8
  35. package/lib/types/services/install/install-types.d.ts +12 -13
  36. package/lib/types/services/install/install.d.ts +1 -1
  37. package/lib/types/services/install/npm-resolve.d.ts +4 -16
  38. package/lib/types/services/install/package-entry.d.ts +6 -11
  39. package/lib/types/services/install/release-target.d.ts +7 -8
  40. package/lib/types/services/install/task-queue.d.ts +25 -30
  41. package/lib/types/services/npm-release-age.d.ts +15 -0
  42. package/lib/types/services/probe.d.ts +4 -4
  43. package/lib/types/services/profile/installed-versions.d.ts +5 -7
  44. package/lib/types/services/profile/profile.d.ts +14 -18
  45. package/lib/types/services/profile/progress.d.ts +7 -10
  46. package/lib/types/services/settings.d.ts +0 -4
  47. package/package.json +1 -1
  48. package/src/client/components/PluginHubSection.tsx +17 -22
  49. package/src/client/components/catalog/CatalogList.tsx +2 -4
  50. package/src/client/components/catalog/PluginCard.tsx +2 -3
  51. package/src/client/components/layout/CatalogControls.tsx +5 -6
  52. package/src/client/components/layout/CatalogHeader.tsx +1 -2
  53. package/src/client/components/layout/CategoryTabs.tsx +2 -3
  54. package/src/client/components/layout/SectionTabs.tsx +3 -5
  55. package/src/client/components/modals/ConfirmDialog.tsx +4 -6
  56. package/src/client/components/modals/InstalledDetailModal.tsx +3 -6
  57. package/src/client/components/modals/LogsModal.tsx +7 -12
  58. package/src/client/components/modals/NotificationsModal.tsx +35 -16
  59. package/src/client/components/modals/ProgressView.tsx +3 -4
  60. package/src/client/components/modals/modals.tsx +35 -15
  61. package/src/client/components/ui/Dropdown.tsx +4 -6
  62. package/src/client/components/ui/Toggle.tsx +2 -3
  63. package/src/client/components/ui/icons.tsx +1 -2
  64. package/src/client/components/views/CustomInstallView.tsx +20 -178
  65. package/src/client/components/views/DiagnosticsView.tsx +3 -6
  66. package/src/client/components/views/InstalledView.tsx +6 -11
  67. package/src/client/components/views/MarketView.tsx +2 -4
  68. package/src/client/components/views/SettingsView.tsx +9 -32
  69. package/src/client/css-modules.d.ts +1 -1
  70. package/src/client/data/catalog.ts +2 -3
  71. package/src/client/data/host.ts +26 -3
  72. package/src/client/data/hub.ts +2 -3
  73. package/src/client/hooks/useCatalog.ts +79 -24
  74. package/src/client/hooks/useIncrementalList.ts +3 -5
  75. package/src/client/hooks/useLanguage.ts +4 -6
  76. package/src/client/hooks/useSettings.ts +4 -11
  77. package/src/client/hooks/useTaskQueue.ts +36 -42
  78. package/src/client/index.tsx +2 -3
  79. package/src/client/locales.ts +75 -114
  80. package/src/client/logic/constants.ts +1 -2
  81. package/src/client/logic/ensure-plugin-css.ts +8 -12
  82. package/src/client/logic/failures.ts +16 -18
  83. package/src/client/logic/format.ts +1 -1
  84. package/src/client/logic/install-command.ts +14 -35
  85. package/src/client/logic/installed.ts +15 -10
  86. package/src/client/logic/normalize.ts +3 -3
  87. package/src/client/logic/release-age.ts +19 -0
  88. package/src/client/logic/urls.ts +20 -20
  89. package/src/client/styles/CustomInstallView.module.css +3 -30
  90. package/src/client/styles/DiagnosticsView.module.css +3 -5
  91. package/src/client/styles/Dropdown.module.css +2 -4
  92. package/src/client/styles/Header.module.css +6 -7
  93. package/src/client/styles/InstalledView.module.css +4 -7
  94. package/src/client/styles/List.module.css +4 -5
  95. package/src/client/styles/LogsView.module.css +2 -3
  96. package/src/client/styles/Modal.module.css +4 -5
  97. package/src/client/styles/SectionTabs.module.css +2 -4
  98. package/src/client/styles/SettingsView.module.css +3 -5
  99. package/src/client/styles/Toggle.module.css +2 -4
  100. package/src/client/styles/tokens.module.css +14 -18
  101. package/src/client/types.ts +11 -11
  102. package/src/server/http/catalog.ts +2 -3
  103. package/src/server/http/diagnostics.ts +6 -7
  104. package/src/server/http/install.ts +47 -208
  105. package/src/server/http/logs.ts +1 -2
  106. package/src/server/http/npm-release-age.ts +42 -0
  107. package/src/server/http/routes.ts +12 -12
  108. package/src/server/http/settings.ts +2 -2
  109. package/src/server/http/shared.ts +5 -6
  110. package/src/server/http/task.ts +1 -2
  111. package/src/server/index.ts +7 -8
  112. package/src/server/services/install/install-types.ts +12 -13
  113. package/src/server/services/install/install.ts +0 -1
  114. package/src/server/services/install/npm-check.ts +3 -2
  115. package/src/server/services/install/npm-resolve.ts +9 -99
  116. package/src/server/services/install/package-entry.ts +6 -11
  117. package/src/server/services/install/registry-check.ts +2 -2
  118. package/src/server/services/install/release-target.ts +8 -9
  119. package/src/server/services/install/task-queue.ts +47 -54
  120. package/src/server/services/npm-release-age.ts +107 -0
  121. package/src/server/services/probe.ts +4 -4
  122. package/src/server/services/profile/installed-versions.ts +16 -22
  123. package/src/server/services/profile/profile.ts +16 -42
  124. package/src/server/services/profile/progress.ts +8 -11
  125. package/src/server/services/settings.ts +2 -9
  126. package/lib/services/install/preflight.js +0 -178
  127. package/lib/types/services/install/preflight.d.ts +0 -23
  128. package/src/server/services/install/preflight.ts +0 -184
@@ -3,15 +3,11 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Notification-center dialog: a persistent log of every settled install /
7
- * remove task — successes and failures alike.
6
+ * 通知中心弹窗:每个已结算的安装/卸载任务(成功与失败)的持久化清单。
8
7
  *
9
- * Records are written to localStorage at settle time (see logic/failures.ts),
10
- * so a result is never lost even when the dialog was dismissed or the user
11
- * was away. Each entry carries a circular status badge (green check for
12
- * success, red cross for failure) with white glyph and message text;
13
- * failures keep their copy / fix / file-an-issue actions. Opened from the
14
- * header entry button (after the Settings tab).
8
+ * 记录在结算时写入 localStorage(见 logic/failures.ts),即使弹窗被关闭或用户离开,
9
+ * 结果也不会丢失。每条带圆形状态徽标(成功绿勾、失败红叉,白色图形)与消息文本;
10
+ * 失败项保留复制/修复/提 Issue 动作。由头部入口按钮(「设置」tab 之后)打开。
15
11
  */
16
12
  import { createElement as h, useState } from 'react'
17
13
  import type { MouseEvent } from 'react'
@@ -336,7 +332,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
336
332
  }
337
333
  if (kind === 'registryVersionStale') {
338
334
  // 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
339
- // 常见于镜像滞后 —— 反查包名走设置里的 npmRegistry,实际安装沿用本机 ~/.npmrc 的源;
335
+ // 常见于镜像滞后 —— 实际安装沿用本机 ~/.npmrc 的源;
340
336
  // 也可能只是版本号写错。与插件作者无关 —— 给对齐源 / 显式走官方源 / 离线包指引,不引导提 Issue
341
337
  return h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
342
338
  }
@@ -367,7 +363,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
367
363
  return h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint', { profile: env?.profile ?? '<profile>' }))
368
364
  }
369
365
  if (kind === 'network') {
370
- // 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
366
+ // 安装前网络探测 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
371
367
  // 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
372
368
  // 不引导提 Issue
373
369
  return h('div', null, [
@@ -384,7 +380,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
384
380
  ])
385
381
  }
386
382
  if (kind === 'pluginPrepare') {
387
- // [packaging](预检/装后校验拦截:git 分发缺产物)与
383
+ // [packaging](装后校验拦截:分发缺产物)与
388
384
  // prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
389
385
  return h('div', null, [
390
386
  h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(r.message) ? t('failPackagingHint') : t('failPrepareHint')),
@@ -395,7 +391,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
395
391
  rel: 'noopener noreferrer',
396
392
  title: t('failIssueHint'),
397
393
  }, t('failIssueBig')) : null,
398
- // 次要入口:这条分支的判据([packaging] 预检/装后校验、prepare 摘要)是插件中心
394
+ // 次要入口:这条分支的判据([packaging] 装后校验、prepare 摘要)是插件中心
399
395
  // 自己产出的,不是 pnpm 原文 —— 判错时用户会被送去作者仓库、我们永远收不到反馈
400
396
  r.repo && shownRepo ? h('div', null, [
401
397
  h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
@@ -433,11 +429,34 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
433
429
  h('div', { className: styles.failPrepareHint }, t('failHostCliSpawnHint')),
434
430
  ])
435
431
  }
436
- // pnpm allowBuilds 拦截与未知失败:不归到插件作者,改为引导到插件中心仓库排查
432
+ if (kind === 'pnpmIgnoredBuild') {
433
+ // 插件的依赖需要在用户机器上编译原生模块,pnpm 默认拦住;我们已自动放行
434
+ // allowBuilds 并重试过一次,仍失败说明任何用户都装不上 —— 属作者侧(发预构建产物 / 走 release 包)
435
+ return h('div', null, [
436
+ h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild')),
437
+ r.repo && shownRepo ? h('a', {
438
+ className: styles.failBigIssue,
439
+ href: pluginIssueUrl(shownRepo, r.message, env, r.command, r.attempts, action),
440
+ target: '_blank',
441
+ rel: 'noopener noreferrer',
442
+ title: t('failIssueHint'),
443
+ }, t('failIssueBig')) : null,
444
+ // 这条判定里也有我们的动作(自动放行过 allowBuilds),判错时得留一条回到插件中心的路
445
+ r.repo && shownRepo ? h('div', null, [
446
+ h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
447
+ h('a', {
448
+ className: styles.failMisjudgeLink,
449
+ href: hubIssueUrl(shownRepo, r.message, env, r.command, r.attempts, 'misjudge', action),
450
+ target: '_blank',
451
+ rel: 'noopener noreferrer',
452
+ title: t('failHubIssueHint'),
453
+ }, t('failMisjudgeLink')),
454
+ ]) : null,
455
+ ])
456
+ }
457
+ // 未知失败:不归到插件作者,改为引导到插件中心仓库排查
437
458
  return h('div', null, [
438
- kind === 'pnpmIgnoredBuild'
439
- ? h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild'))
440
- : h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
459
+ h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
441
460
  h('a', {
442
461
  className: styles.failBigIssue,
443
462
  href: hubIssueUrl(shownRepo || r.repo, r.message, env, r.command, r.attempts, 'triage', action),
@@ -3,10 +3,9 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Inline progress block shared by the install and uninstall dialogs:
7
- * a real percentage bar (server-estimated, client-polled). No live terminal
8
- * window — pnpm emits nothing during git clone/build, so a log pane would be
9
- * empty for most of the run; failures surface their full output in ErrorModal.
6
+ * 安装与卸载弹窗共用的内联进度块:真实百分比进度条(服务端估算、客户端轮询)。
7
+ * 不做实时终端窗口 —— pnpm 在 git clone/构建期间几乎无输出,日志面板大部分时间是空的;
8
+ * 失败时完整输出在 ErrorModal 里展示。
10
9
  */
11
10
  import { createElement as h } from 'react'
12
11
  import styles from '../../styles/Modal.module.css'
@@ -3,11 +3,9 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Dialog layer for the Plugin Hub: the install-confirm dialog, the uninstall
7
- * dialog and the global toast. Both dialogs lock themselves while a mutation
8
- * is running, then switch to a result view with a single action — "done" for
9
- * the common case (the page refreshes on close so the change applies), or a
10
- * single "restart now" for the rare changes a refresh cannot pick up.
6
+ * 插件中心的弹窗层:安装确认弹窗、卸载弹窗与全局 Toast。两个弹窗在变更执行期间
7
+ * 自我锁定,随后切到只有一个动作的结果视图 —— 常见情况给「完成」(关闭时刷新页面即生效),
8
+ * 刷新解决不了的少数情况才给单个「立即重启」。
11
9
  */
12
10
  import { createElement as h } from 'react'
13
11
  import type { MouseEvent } from 'react'
@@ -343,7 +341,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
343
341
  repo: string | null
344
342
  action: FailureAction
345
343
  command?: string
346
- /** 尝试过的安装方式(npm 反查 + 实际执行命令):issue 预填一并贴给作者,便于反推正确的 npm 包名 */
344
+ /** 实际执行/重试过的安装命令:issue 预填一并贴给作者 */
347
345
  attempts?: string[]
348
346
  t: Translate
349
347
  env: EnvInfo | null
@@ -352,7 +350,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
352
350
  /** 网络不通时的「去系统诊断」直达按钮:跳到设置 → 系统诊断跑连通性检测(由宿主提供) */
353
351
  onRunDiagnostics?: () => void
354
352
  }) {
355
- // 失败归类:当前安装通道(npm/git)装不上 = 插件分发/依赖的问题,一律引导提 Issue
353
+ // 失败归类:装不上 = 插件分发/依赖的问题,一律引导提 Issue
356
354
  const failureKind = classifyFailure(message)
357
355
  // 动作词:标题、徽标与提示里的 {action} 统一取这一个词
358
356
  const actionWord = t(action === 'uninstall' ? 'actionUninstall' : action === 'update' ? 'actionUpdate' : 'actionInstall')
@@ -469,7 +467,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
469
467
  ])
470
468
  : failureKind === 'registryVersionStale'
471
469
  // 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
472
- // 常见于镜像滞后 —— 反查包名走设置里的 npmRegistry,实际安装沿用本机 ~/.npmrc 的源,
470
+ // 常见于镜像滞后 —— 实际安装沿用本机 ~/.npmrc 的源,
473
471
  // 官方源刚发布而镜像还没同步;也可能只是版本号写错。与插件作者无关 ——
474
472
  // 给「对齐源 / 那一次显式走官方源 / 用离线包」的指引,不引导提 Issue
475
473
  ? h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
@@ -492,7 +490,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
492
490
  // 安装流程,与插件无关 —— 引导从本机地址重新打开市场,不引导提 Issue
493
491
  ? h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint', { profile: env?.profile ?? '<profile>' }))
494
492
  : failureKind === 'network'
495
- // 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
493
+ // 安装前网络探测 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
496
494
  // 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
497
495
  // 不引导提 Issue
498
496
  ? h('div', null, [
@@ -509,7 +507,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
509
507
  ])
510
508
  : failureKind === 'pluginPrepare'
511
509
  ? h('div', null, [
512
- // [packaging](预检/装后校验拦截:git 分发缺产物)与
510
+ // [packaging](装后校验拦截:分发缺产物)与
513
511
  // prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
514
512
  h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(message) ? t('failPackagingHint') : t('failPrepareHint')),
515
513
  repo ? h('a', {
@@ -519,7 +517,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
519
517
  rel: 'noopener noreferrer',
520
518
  title: t('failIssueHint'),
521
519
  }, t('failIssueBig')) : null,
522
- // 次要入口:这条分支的判据([packaging] 预检/装后校验、prepare 摘要)是插件中心
520
+ // 次要入口:这条分支的判据([packaging] 装后校验、prepare 摘要)是插件中心
523
521
  // 自己产出的,不是 pnpm 原文 —— 判错时用户会被送去作者仓库、我们永远收不到反馈,
524
522
  // 故另给一条「可能是市场误判」的反馈通道(正文自我说明它来自哪条分支)
525
523
  repo ? h('div', null, [
@@ -553,11 +551,33 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
553
551
  // 宿主自身入口起不来:环境问题,给提示、不引导提 Issue
554
552
  h('div', { className: styles.failPrepareHint }, t('failHostCliSpawnHint')),
555
553
  ])
554
+ : failureKind === 'pnpmIgnoredBuild'
555
+ // 该类型的插件依赖需要在用户机器上编译原生模块,pnpm 默认拦住;我们已自动放行
556
+ // allowBuilds 并重试过一次,仍失败说明任何用户都装不上 —— 属作者侧(发预构建产物 / 走 release 包)。
557
+ ? h('div', null, [
558
+ h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild')),
559
+ repo ? h('a', {
560
+ className: styles.failBigIssue,
561
+ href: pluginIssueUrl(repo, message, env, command, attempts, action),
562
+ target: '_blank',
563
+ rel: 'noopener noreferrer',
564
+ title: t('failIssueHint'),
565
+ }, t('failIssueBig')) : null,
566
+ // 这条判定里也有我们的动作(我们自动放行过 allowBuilds),判错时得留一条回到插件中心的路
567
+ repo ? h('div', null, [
568
+ h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
569
+ h('a', {
570
+ className: styles.failMisjudgeLink,
571
+ href: hubIssueUrl(repo, message, env, command, attempts, 'misjudge', action),
572
+ target: '_blank',
573
+ rel: 'noopener noreferrer',
574
+ title: t('failHubIssueHint'),
575
+ }, t('failMisjudgeLink')),
576
+ ]) : null,
577
+ ])
556
578
  : h('div', null, [
557
- // pnpm allowBuilds 拦截与未知失败:不归到插件作者,改为引导到插件中心仓库排查
558
- failureKind === 'pnpmIgnoredBuild'
559
- ? h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild'))
560
- : h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
579
+ // 未分类(hub):不归到插件作者,改为引导到插件中心仓库分诊
580
+ h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
561
581
  h('a', {
562
582
  className: styles.failBigIssue,
563
583
  href: hubIssueUrl(repo, message, env, command, attempts, 'triage', action),
@@ -3,13 +3,11 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Button-style dropdown (native <select> replacement).
6
+ * 按钮式下拉框(替代原生 <select>)。
7
7
  *
8
- * The trigger is a compact toolbar button — hairline border + subtle grey
9
- * fill + current value + chevron — so it sits naturally next to the other
10
- * toolbar buttons and takes a single slot of horizontal space. Clicking
11
- * outside or pressing Esc closes the panel; options get a hover fill and
12
- * the active one a brand highlight, all driven by the --hub-* tokens.
8
+ * 触发器是紧凑的工具栏按钮 —— 细边框 + 淡灰填充 + 当前值 + 箭头 —— 与其它工具栏按钮
9
+ * 并排自然,只占一格横向空间。点击外部或按 Esc 关闭面板;选项有 hover 填充、
10
+ * 激活项为品牌高亮,全部由 --hub-* 变量驱动。
13
11
  */
14
12
  import { createElement as h, useEffect, useRef, useState } from 'react'
15
13
  import type { JSX } from 'react'
@@ -3,9 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Compact switch used by the settings rows. Native-button based so it is
7
- * keyboard focusable and announces state via aria-checked; styling driven
8
- * by the --hub-* tokens (on = brand fill, off = neutral track).
6
+ * 设置行用的紧凑开关。基于原生 button,可键盘聚焦并通过 aria-checked 播报状态;
7
+ * 样式由 --hub-* 变量驱动(开 = 品牌填充,关 = 中性轨道)。
9
8
  */
10
9
  import { createElement as h } from 'react'
11
10
  import styles from '../../styles/Toggle.module.css'
@@ -3,8 +3,7 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Inline SVG icons used across the client. Icons inherit currentColor so
7
- * they adapt to themes and disabled states automatically.
6
+ * 客户端各处使用的内联 SVG 图标。图标继承 currentColor,自动适配主题与禁用态。
8
7
  */
9
8
  import { createElement as h } from 'react'
10
9
  import headerStyles from '../../styles/Header.module.css'
@@ -3,18 +3,12 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Custom install view: a standalone top-level section for manually
7
- * installing npm packages or GitHub sources outside the catalog. Three
8
- * installer cards, one per channel — npm package (bare name or a full
9
- * npm/pnpm install command, including the official global `npm install -g`),
10
- * GitHub source and the official `dsh plugin ... add <target>` command —
11
- * each with a placeholder example above the input and its own format check.
12
- * The cards stay separate so a user never has to guess which format goes
13
- * where. Finished installs surface in the Installed view, marked as custom.
6
+ * 自定义安装视图:独立一级板块,用于手动安装目录之外的 npm 包。安装卡片接受裸包名或完整的
7
+ * npm/pnpm 安装命令(含官方全局 `npm install -g`),输入框上方带示例并自带格式校验。
8
+ * 装完的插件出现在「已安装」视图,标记为自定义安装。
14
9
  *
15
- * Pure presentational: owns only the three local input/error states; the
16
- * actual install (custom source, gated by the security toggles) bubbles up
17
- * via onInstallCustom.
10
+ * 纯展示组件:只持有本地输入/错误状态;真正的安装(custom 源,受安全开关控制)经
11
+ * onInstallCustom 上抛。
18
12
  */
19
13
  import { createElement as h, useState } from 'react'
20
14
  import type { FormEvent, MouseEvent } from 'react'
@@ -23,27 +17,14 @@ import modal from '../../styles/Modal.module.css'
23
17
  import type { InstallChannel, Translate } from '../../types.ts'
24
18
  import { CloseIcon, HelpIcon } from '../ui/icons.tsx'
25
19
 
26
- /* —— 自定义安装输入预检 ——
27
- * 提交前校验「输入是否符合该通道格式」,识别不了的输入不发请求,就地提示推荐格式。
28
- * 三个通道严格分开:npm 框认裸包名或完整 npm/pnpm 安装命令(含全局 `npm install -g`),
29
- * git 框只认 GitHub 地址,命令框只认官方 `dsh plugin --profile <name> <add|update> <target>`
30
- * (--profile 必填,无 -p 简写;update = 更新已安装目标到最新)。
20
+ /* —— 自定义安装输入校验 ——
21
+ * 提交前校验「输入是否符合格式」,识别不了的输入不发请求,就地提示推荐格式。
22
+ * npm 框认裸包名或完整 npm/pnpm 安装命令(含全局 `npm install -g`)。
31
23
  * 这里只做格式校验,真正归一化/安装由服务端完成。 */
32
24
 
33
- /** GitHub 地址(带前缀形态):github:、https://github.com/、git+https://github.com/、git@github.com: */
34
- const GITHUB_URL_RE = /^(?:github:|https?:\/\/github\.com\/|git\+https?:\/\/github\.com\/|git@github\.com:)([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/
35
- /** 裸 owner/repo(npm 包名只有 @scope/pkg 才带斜杠,无 @ 前缀的 a/b 一定是 GitHub 仓库) */
36
- const GITHUB_BARE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/
37
25
  /** npm 包名(含 @scope/pkg) */
38
26
  const NPM_PACKAGE_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/
39
27
 
40
- /** DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> <add|update> <target>`)—— 提取其中的动作与目标。
41
- * 官方 CLI(apps/cli/src/args.ts,commander):--profile 为必填长选项(无 -p 简写,省略即报错),
42
- * 支持 `--profile=<name>` 等号形式;命令大小写不敏感。update = 已安装目标覆盖更新到最新;
43
- * remove 不走这里(卸载由「已安装」列表的卸载按钮执行,命令框不认卸载动词)。
44
- * 与 server 端 installTargetOf 口径一致。 */
45
- const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+(add|update)\s+(.+)$/i
46
-
47
28
  /** npm 全局安装命令(官方 README 格式,如 `npm install -g @deepseek-ai/dsh @deepseek-harness-tui/dsh-tui`)——
48
29
  * 提取 -g 之后的包列表;任一包名非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 口径一致。 */
49
30
  const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i
@@ -52,14 +33,6 @@ const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i
52
33
  * 提取命令后的包列表;包列表在 splitPackages 里逐个校验。 */
53
34
  const NPM_INSTALL_CMD_RE = /^(?:npm\s+(?:install|i)|pnpm\s+(?:add|i|install))\s+(.+)$/i
54
35
 
55
- /** 解析 DSH 命令:返回 { action, target };非官方命令返回 null(交给格式错误提示)。 */
56
- function parseDshCommand(raw: string): { action: 'add' | 'update'; target: string } | null {
57
- const input = raw.trim()
58
- const match = DSH_PLUGIN_CMD_RE.exec(input)
59
- if (match === null) return null
60
- return { action: match[1].toLowerCase() as 'add' | 'update', target: match[2].trim() }
61
- }
62
-
63
36
  /** 空格分隔的包列表:逐个按 npm 包名语法校验 + 拒绝以 - 开头的 token(npm 会把 --xxx 当参数而非包名),
64
37
  * 任一非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 同口径。 */
65
38
  function splitPackages(raw: string): string[] | null {
@@ -96,46 +69,28 @@ function parseNpmInput(raw: string): NpmInputParse | null {
96
69
  return null
97
70
  }
98
71
 
99
- function isGitHubInput(raw: string): boolean {
100
- const input = raw.trim()
101
- return GITHUB_URL_RE.test(input) || GITHUB_BARE_RE.test(input)
102
- }
103
-
104
- /** 卡片标题旁的「帮助」弹窗:三块卡片各自的标题与格式清单语言包 key。 */
72
+ /** 卡片标题旁的「帮助」弹窗:该卡片的标题与格式清单语言包 key。 */
105
73
  const HELP_CARDS = {
106
74
  npm: { titleKey: 'npmInstallLabel', contentKey: 'npmInstallHelp' },
107
- git: { titleKey: 'gitInstallLabel', contentKey: 'gitInstallHelp' },
108
- cmd: { titleKey: 'dshCmdLabel', contentKey: 'dshCmdHelp' },
109
75
  } as const
110
76
 
111
77
  type HelpTarget = keyof typeof HELP_CARDS
112
78
 
113
- export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, enableDsh, onOpenSettings, profile }: {
79
+ export function CustomInstallView({ t, onInstallCustom, enableNpm, onOpenSettings }: {
114
80
  t: Translate
115
- /** 命令行安装:输入 npm 包名 / npm、pnpm 安装命令 / GitHub 地址 / dsh plugin 命令即装
116
- * (custom 源,受安全设置开关控制)。installChannel 必填 = 入口渠道(三张卡片之一):
117
- * 服务端日志/报错提示据此溯源「从哪个入口发起」;opts.globalNpm 表示官方 npm 全局安装
118
- * 命令(npm install -g),附带解析出的包列表(仍属 NPM 包入口)。 */
81
+ /** 命令行安装:输入 npm 包名 / npm、pnpm 安装命令即装(custom 源,受安全设置开关控制)。
82
+ * installChannel 必填 = 入口渠道(NPM 包卡片):服务端日志/报错提示据此溯源「从哪个入口发起」;
83
+ * opts.globalNpm 表示官方 npm 全局安装命令(npm install -g),附带解析出的包列表。 */
119
84
  onInstallCustom: (raw: string, opts: { installChannel: InstallChannel; globalNpm?: string[] }) => void
120
- /** 安全信任三通道开关:关掉后对应输入卡片禁用(不提交),并引导去设置打开 */
85
+ /** 安全信任开关:关掉后输入卡片禁用(不提交),并引导去设置打开 */
121
86
  enableNpm: boolean
122
- enableGit: boolean
123
- enableDsh: boolean
124
87
  /** 引导去「设置 → 安全信任」打开被关闭的通道开关 */
125
88
  onOpenSettings: () => void
126
- /** 当前 dsh profile 名(宿主 /env 快照):一键插入的更新命令用它拼 --profile */
127
- profile: string
128
89
  }) {
129
90
  /** 自定义安装:NPM 包输入与格式错误('' = 无错误) */
130
91
  const [npmQuery, setNpmQuery] = useState('')
131
92
  const [npmError, setNpmError] = useState('')
132
- /** 自定义安装:GitHub 源码输入与格式错误('' = 无错误) */
133
- const [gitQuery, setGitQuery] = useState('')
134
- const [gitError, setGitError] = useState('')
135
- /** 自定义安装:DSH 命令输入与格式错误('' = 无错误) */
136
- const [cmdQuery, setCmdQuery] = useState('')
137
- const [cmdError, setCmdError] = useState('')
138
- /** 卡片标题旁「帮助」弹窗:null = 未打开;'npm' | 'git' | 'cmd' = 打开对应卡片的格式清单 */
93
+ /** 卡片标题旁「帮助」弹窗:null = 未打开;'npm' = 打开该卡片的格式清单 */
139
94
  const [helpFor, setHelpFor] = useState<HelpTarget | null>(null)
140
95
 
141
96
  /** 通道关闭提示行:开关关掉后替代示例与输入行,引导去设置打开 */
@@ -148,9 +103,8 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
148
103
  }, t('goToSettings')),
149
104
  )
150
105
 
151
- // 三个通道分开校验 —— 识别不了的输入不发请求,就地提示推荐写法。
152
- // NPM 框认裸包名或完整 npm/pnpm 命令(含全局 npm install -g,走 globalNpm 通道);
153
- // 命令框只认官方 dsh plugin 命令,提交时剥成裸目标发给服务端。
106
+ // 只做格式校验 —— 识别不了的输入不发请求,就地提示推荐写法。
107
+ // NPM 框认裸包名或完整 npm/pnpm 命令(含全局 npm install -g,走 globalNpm 通道)。
154
108
  const submitNpm = () => {
155
109
  const raw = npmQuery.trim()
156
110
  if (!raw) return
@@ -164,36 +118,12 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
164
118
  setNpmQuery('')
165
119
  setNpmError('')
166
120
  }
167
- const submitGit = () => {
168
- const target = gitQuery.trim()
169
- if (!target) return
170
- if (!isGitHubInput(target)) { setGitError(t('gitInstallInvalid')); return }
171
- onInstallCustom(target, { installChannel: 'git' })
172
- setGitQuery('')
173
- setGitError('')
174
- }
175
- const submitCmd = () => {
176
- const raw = cmdQuery.trim()
177
- if (!raw) return
178
- const parsed = parseDshCommand(raw)
179
- if (parsed === null) { setCmdError(t('dshCmdInvalid', { profile })); return }
180
- // add / update 统一走安装流程:目标已安装时弹窗自动转「更新」覆盖重装(服务端 mode=update 放行),
181
- // 未安装时即为普通安装 —— 粘贴 `dsh plugin --profile web update dsh-plugin` 这类命令即可更新到最新
182
- onInstallCustom(parsed.target, { installChannel: 'dsh' })
183
- setCmdQuery('')
184
- setCmdError('')
185
- }
186
- /** 一键插入 DSH Plugin Hub 自身的更新命令:按当前 profile 拼官方命令填进输入框,用户确认后提交 */
187
- const insertHubUpdate = () => {
188
- setCmdQuery(`dsh plugin --profile ${profile} update dsh-plugin`)
189
- setCmdError('')
190
- }
191
121
 
192
122
  return h('div', { className: styles.root },
193
123
  // 页面说明:独立一级导航,交代「装完去哪看」
194
124
  h('p', { className: styles.desc }, t('customViewDesc')),
195
- // 三块卡片(一行一个):label + 文本框上方示例 + 输入行 + 错误区;
196
- // 安全信任开关关掉对应通道时,卡片禁用并显示「前往设置」提示行
125
+ // NPM 包卡片:label + 文本框上方示例 + 输入行 + 错误区;
126
+ // 安全信任开关关掉时,卡片禁用并显示「前往设置」提示行
197
127
  h('div', { className: styles.installCards },
198
128
  // NPM 包:输入包名或原生 npm/pnpm 安装命令即装(含官方全局 npm install -g)
199
129
  h('div', { className: enableNpm ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
@@ -239,94 +169,6 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
239
169
  npmError ? h('div', { className: styles.installError }, npmError) : null,
240
170
  ],
241
171
  ),
242
- // GitHub 源码:输入仓库地址即装
243
- h('div', { className: enableGit ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
244
- h('div', { className: styles.installCardHead },
245
- h('span', { className: styles.installLabel }, t('gitInstallLabel')),
246
- h('button', {
247
- type: 'button',
248
- className: styles.installHelpBtn,
249
- title: t('installHelp'),
250
- 'aria-label': t('installHelp'),
251
- onClick: () => setHelpFor('git'),
252
- }, h(HelpIcon)),
253
- ),
254
- !enableGit
255
- ? channelOffRow
256
- : [
257
- h('div', { className: styles.installExample }, t('gitInstallExample')),
258
- h('div', { className: styles.installRow },
259
- h('input', {
260
- className: gitError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
261
- type: 'text',
262
- placeholder: t('gitInstallPlaceholder'),
263
- value: gitQuery,
264
- spellCheck: false,
265
- onInput: (e: FormEvent<HTMLInputElement>) => {
266
- setGitQuery((e.target as HTMLInputElement).value)
267
- setGitError('')
268
- },
269
- onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitGit() },
270
- }),
271
- h('button', {
272
- className: styles.installBtn,
273
- type: 'button',
274
- disabled: gitQuery.trim() === '',
275
- onClick: submitGit,
276
- }, t('installCliBtn')),
277
- ),
278
- gitError ? h('div', { className: styles.installError }, gitError) : null,
279
- ],
280
- ),
281
- // 命令卡片:粘贴官方 dsh plugin 命令即装/即更新 —— 只认完整命令
282
- // (--profile 必填,可含 = 等号,动词 add/update,剥成裸目标走安装流程)
283
- h('div', { className: enableDsh ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
284
- h('div', { className: styles.installCardHead },
285
- h('span', { className: styles.installLabel }, t('dshCmdLabel')),
286
- h('button', {
287
- type: 'button',
288
- className: styles.installHelpBtn,
289
- title: t('installHelp'),
290
- 'aria-label': t('installHelp'),
291
- onClick: () => setHelpFor('cmd'),
292
- }, h(HelpIcon)),
293
- // 一键插入 Hub 自身的更新命令:按当前 profile 拼 `dsh plugin --profile <p> update dsh-plugin`
294
- // 填入输入框(命令卡片被安全开关禁用时不显示,插入后也提交不了)
295
- enableDsh
296
- ? h('button', {
297
- type: 'button',
298
- className: styles.installInsertBtn,
299
- onClick: insertHubUpdate,
300
- }, t('dshCmdInsertHubUpdate'))
301
- : null,
302
- ),
303
- !enableDsh
304
- ? channelOffRow
305
- : [
306
- h('div', { className: styles.installExample }, t('dshCmdExample', { profile })),
307
- h('div', { className: styles.installRow },
308
- h('input', {
309
- className: cmdError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
310
- type: 'text',
311
- placeholder: t('dshCmdPlaceholder'),
312
- value: cmdQuery,
313
- spellCheck: false,
314
- onInput: (e: FormEvent<HTMLInputElement>) => {
315
- setCmdQuery((e.target as HTMLInputElement).value)
316
- setCmdError('')
317
- },
318
- onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitCmd() },
319
- }),
320
- h('button', {
321
- className: styles.installBtn,
322
- type: 'button',
323
- disabled: cmdQuery.trim() === '',
324
- onClick: submitCmd,
325
- }, t('installCliBtn')),
326
- ),
327
- cmdError ? h('div', { className: styles.installError }, cmdError) : null,
328
- ],
329
- ),
330
172
  ),
331
173
  // 帮助弹窗:点卡片标题旁的问号打开 —— 宽窗(720px)把该通道支持的命令格式整行展示,防止折行
332
174
  helpFor !== null
@@ -346,7 +188,7 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
346
188
  h('div', { className: modal.modalBody },
347
189
  // 格式清单:一行一条;`命令\t说明` 的说明跟在命令后,纯说明行以 \t 开头
348
190
  h('div', { className: styles.helpBody },
349
- t(HELP_CARDS[helpFor].contentKey, { profile }).split('\n').map((line, i) => {
191
+ t(HELP_CARDS[helpFor].contentKey).split('\n').map((line, i) => {
350
192
  const [cmd, note] = line.split('\t')
351
193
  return h('div', { key: i, className: styles.helpLine },
352
194
  cmd !== '' ? h('span', { className: styles.helpCmd }, cmd) : null,
@@ -3,12 +3,9 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * System diagnostics list: one row per connectivity channel (npm registry /
7
- * GitHub API / catalog site), each with a live status badge — 200 OK in
8
- * green, Unreachable in red, Checking… while probing. The list runs
9
- * automatically when the page opens; every row is a button that re-probes
10
- * just that channel, so a click always yields visible feedback
11
- * (row pulses → badge flips → latency refreshes).
6
+ * 系统诊断列表:每个连通性通道(npm registry / GitHub API / 目录站点)一行,各带实时
7
+ * 状态徽标 —— 200 OK 绿、不可达红、探测中显示「检测中…」。页面打开时自动跑一轮;
8
+ * 每行都是按钮,点击只重测该通道,因此点击总有可见反馈(行脉冲 → 徽标翻转 → 延迟刷新)。
12
9
  */
13
10
  import { createElement as h, useCallback, useEffect, useRef, useState } from 'react'
14
11
  import styles from '../../styles/DiagnosticsView.module.css'
@@ -3,18 +3,13 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Installed view: manages the plugins installed into the current profile.
7
- * Follows the mainstream extension-manager pattern (VSCode Installed tab) —
8
- * a searchable, sortable list of installed entries that mixes catalog
9
- * plugins (full metadata) and custom command installs (outside the catalog,
10
- * only runtime info, tagged with a custom badge). The toolbar carries a
11
- * search box plus two segmented radio-button groups: source filter
12
- * (all / catalog / custom) and sort (name / recently installed / stars / forks).
13
- * Each row carries restart (when pending), update and uninstall actions
14
- * and opens a detail modal with the full install metadata.
6
+ * 已安装视图:管理安装进当前 profile 的插件。沿用主流扩展管理器模式(VSCode 已安装 tab)——
7
+ * 可搜索、可排序的已安装清单,混排目录插件(完整元数据)与命令行自定义安装(目录之外,
8
+ * 只有运行时信息,打「自定义」徽标)。工具栏含搜索框与两组分段单选:来源筛选
9
+ * (全部/目录/自定义)与排序(名称/最近安装/Star/Fork)。每行带重启(待重启时)、更新与
10
+ * 卸载动作,并打开含完整安装元数据的详情弹窗。
15
11
  *
16
- * Pure presentational: owns only local search/sort state; the InstalledItem
17
- * model arrives from the catalog hook and actions bubble up via callbacks.
12
+ * 纯展示组件:只持有本地搜索/排序状态;InstalledItem 模型来自目录 hook,动作经回调上抛。
18
13
  */
19
14
  import { createElement as h, useMemo, useState } from 'react'
20
15
  import type { FormEvent, MouseEvent, ReactNode } from 'react'
@@ -3,10 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Market view: the browse experience for the catalog — category tabs,
7
- * search/sort/install-state controls and the plugin list. Owns nothing;
8
- * it wires catalog state (query/sort/filter/category) to the small
9
- * presentational components so PluginHubSection stays free of view internals.
6
+ * 市场视图:目录的浏览体验 —— 分类 Tab、搜索/排序/安装状态控件与插件列表。自身不持有状态,
7
+ * 只把目录状态(query/sort/filter/category)接到小型展示组件上,让 PluginHubSection 不掺视图细节。
10
8
  */
11
9
  import { createElement as h, Fragment } from 'react'
12
10
  import type { HubPlugin, LocaleId } from '../../types.ts'