dsh-plugin 1.6.12 → 2.0.1

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 (131) hide show
  1. package/README.en.md +13 -13
  2. package/README.md +13 -13
  3. package/client/client.js +791 -761
  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/operation-log.js +53 -1
  20. package/lib/services/install/package-entry.js +6 -11
  21. package/lib/services/install/registry-check.js +2 -2
  22. package/lib/services/install/release-target.js +8 -9
  23. package/lib/services/install/task-queue.js +44 -48
  24. package/lib/services/npm-release-age.js +119 -0
  25. package/lib/services/probe.js +4 -4
  26. package/lib/services/profile/installed-versions.js +15 -21
  27. package/lib/services/profile/profile.js +16 -44
  28. package/lib/services/profile/progress.js +8 -11
  29. package/lib/services/settings.js +2 -5
  30. package/lib/types/http/catalog.d.ts +1 -1
  31. package/lib/types/http/install.d.ts +2 -2
  32. package/lib/types/http/npm-release-age.d.ts +2 -0
  33. package/lib/types/http/routes.d.ts +10 -12
  34. package/lib/types/http/shared.d.ts +5 -6
  35. package/lib/types/index.d.ts +7 -8
  36. package/lib/types/services/install/install-types.d.ts +12 -13
  37. package/lib/types/services/install/install.d.ts +1 -1
  38. package/lib/types/services/install/npm-resolve.d.ts +4 -16
  39. package/lib/types/services/install/operation-log.d.ts +9 -0
  40. package/lib/types/services/install/package-entry.d.ts +6 -11
  41. package/lib/types/services/install/release-target.d.ts +7 -8
  42. package/lib/types/services/install/task-queue.d.ts +25 -30
  43. package/lib/types/services/npm-release-age.d.ts +15 -0
  44. package/lib/types/services/probe.d.ts +4 -4
  45. package/lib/types/services/profile/installed-versions.d.ts +5 -7
  46. package/lib/types/services/profile/profile.d.ts +14 -18
  47. package/lib/types/services/profile/progress.d.ts +7 -10
  48. package/lib/types/services/settings.d.ts +0 -4
  49. package/package.json +1 -1
  50. package/src/client/components/PluginHubSection.tsx +26 -22
  51. package/src/client/components/catalog/CatalogList.tsx +2 -4
  52. package/src/client/components/catalog/PluginCard.tsx +2 -3
  53. package/src/client/components/layout/CatalogControls.tsx +5 -6
  54. package/src/client/components/layout/CatalogHeader.tsx +1 -2
  55. package/src/client/components/layout/CategoryTabs.tsx +2 -3
  56. package/src/client/components/layout/SectionTabs.tsx +3 -5
  57. package/src/client/components/modals/ConfirmDialog.tsx +4 -6
  58. package/src/client/components/modals/InstalledDetailModal.tsx +3 -6
  59. package/src/client/components/modals/LogsModal.tsx +7 -12
  60. package/src/client/components/modals/NotificationsModal.tsx +74 -24
  61. package/src/client/components/modals/ProgressView.tsx +3 -4
  62. package/src/client/components/modals/modals.tsx +75 -26
  63. package/src/client/components/ui/Dropdown.tsx +4 -6
  64. package/src/client/components/ui/Toggle.tsx +2 -3
  65. package/src/client/components/ui/icons.tsx +1 -2
  66. package/src/client/components/views/CustomInstallView.tsx +20 -178
  67. package/src/client/components/views/DiagnosticsView.tsx +3 -6
  68. package/src/client/components/views/InstalledView.tsx +6 -11
  69. package/src/client/components/views/MarketView.tsx +2 -4
  70. package/src/client/components/views/SettingsView.tsx +9 -32
  71. package/src/client/css-modules.d.ts +1 -1
  72. package/src/client/data/catalog.ts +2 -3
  73. package/src/client/data/host.ts +26 -3
  74. package/src/client/data/hub.ts +2 -3
  75. package/src/client/hooks/useCatalog.ts +79 -24
  76. package/src/client/hooks/useIncrementalList.ts +3 -5
  77. package/src/client/hooks/useLanguage.ts +4 -6
  78. package/src/client/hooks/useSettings.ts +4 -11
  79. package/src/client/hooks/useTaskQueue.ts +36 -42
  80. package/src/client/index.tsx +2 -3
  81. package/src/client/locales.ts +187 -114
  82. package/src/client/logic/constants.ts +1 -2
  83. package/src/client/logic/ensure-plugin-css.ts +8 -12
  84. package/src/client/logic/failures.ts +20 -19
  85. package/src/client/logic/format.ts +1 -1
  86. package/src/client/logic/install-command.ts +14 -35
  87. package/src/client/logic/installed.ts +15 -10
  88. package/src/client/logic/normalize.ts +3 -3
  89. package/src/client/logic/release-age.ts +19 -0
  90. package/src/client/logic/urls.ts +24 -21
  91. package/src/client/styles/CustomInstallView.module.css +3 -30
  92. package/src/client/styles/DiagnosticsView.module.css +3 -5
  93. package/src/client/styles/Dropdown.module.css +2 -4
  94. package/src/client/styles/Header.module.css +6 -7
  95. package/src/client/styles/InstalledView.module.css +4 -7
  96. package/src/client/styles/List.module.css +4 -5
  97. package/src/client/styles/LogsView.module.css +2 -3
  98. package/src/client/styles/Modal.module.css +46 -6
  99. package/src/client/styles/SectionTabs.module.css +2 -4
  100. package/src/client/styles/SettingsView.module.css +3 -5
  101. package/src/client/styles/Toggle.module.css +2 -4
  102. package/src/client/styles/tokens.module.css +14 -18
  103. package/src/client/types.ts +11 -11
  104. package/src/server/http/catalog.ts +2 -3
  105. package/src/server/http/diagnostics.ts +6 -7
  106. package/src/server/http/install.ts +47 -208
  107. package/src/server/http/logs.ts +1 -2
  108. package/src/server/http/npm-release-age.ts +42 -0
  109. package/src/server/http/routes.ts +12 -12
  110. package/src/server/http/settings.ts +2 -2
  111. package/src/server/http/shared.ts +5 -6
  112. package/src/server/http/task.ts +1 -2
  113. package/src/server/index.ts +7 -8
  114. package/src/server/services/install/install-types.ts +12 -13
  115. package/src/server/services/install/install.ts +0 -1
  116. package/src/server/services/install/npm-check.ts +3 -2
  117. package/src/server/services/install/npm-resolve.ts +9 -99
  118. package/src/server/services/install/operation-log.ts +47 -1
  119. package/src/server/services/install/package-entry.ts +6 -11
  120. package/src/server/services/install/registry-check.ts +2 -2
  121. package/src/server/services/install/release-target.ts +8 -9
  122. package/src/server/services/install/task-queue.ts +51 -54
  123. package/src/server/services/npm-release-age.ts +107 -0
  124. package/src/server/services/probe.ts +4 -4
  125. package/src/server/services/profile/installed-versions.ts +16 -22
  126. package/src/server/services/profile/profile.ts +16 -42
  127. package/src/server/services/profile/progress.ts +8 -11
  128. package/src/server/services/settings.ts +2 -9
  129. package/lib/services/install/preflight.js +0 -178
  130. package/lib/types/services/install/preflight.d.ts +0 -23
  131. 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'
@@ -206,14 +202,19 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
206
202
  ? (isUpdate
207
203
  ? h('span', { className: styles.failRepo, title: r.repo }, r.repo)
208
204
  : shownRepo
209
- ? h('a', {
210
- className: styles.failRepo,
211
- // 跳转到官网详情页(含插件收录信息),不在通知弹窗直接跳 GitHub
212
- href: pluginSiteUrl(shownRepo),
213
- target: '_blank',
214
- rel: 'noopener noreferrer',
215
- title: shownRepo,
216
- }, shownRepo)
205
+ ? (() => {
206
+ const url = pluginSiteUrl(shownRepo)
207
+ return h('a', {
208
+ className: styles.failRepo,
209
+ // 跳官网「插件详情」页;文字里直接给出目标地址(去掉协议)
210
+ href: url,
211
+ target: '_blank',
212
+ rel: 'noopener noreferrer',
213
+ title: url,
214
+ },
215
+ h('span', { className: styles.failRepoLabel }, `${t('detailTitle')} · `),
216
+ h('span', { className: styles.failRepoUrl }, url.replace(/^https?:\/\//, '').split('?')[0]))
217
+ })()
217
218
  // 反查不到仓库身份(如命令行安装的裸 npm 包名):回退显示记录里的原始值,
218
219
  // 保证通知永不只剩一句「安装/更新成功」没有名字 —— 只是不给官网外链(查不到不敢乱跳)
219
220
  : h('span', { className: styles.failRepo, title: r.repo }, r.repo))
@@ -251,6 +252,16 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
251
252
  onClick: (e: MouseEvent<HTMLButtonElement>) => { e.stopPropagation(); setConfirmRemoveId(r.id) },
252
253
  }, h(CloseIcon)),
253
254
  ),
255
+ // 统一失败卡片头部:报错类型 + 一句话原因(具体指引在下面的分支里)
256
+ !r.ok && (() => {
257
+ const k = classifyFailure(r.message)
258
+ return h('div', null, [
259
+ h('div', { className: styles.failKindTitle }, t(`failKind_${k}`, k === 'hostIncompatible'
260
+ ? { version: /incompatible with dsh\s+([^\s:;,]+)/i.exec(r.message ?? '')?.[1] ?? '' }
261
+ : undefined)),
262
+ k === 'hostIncompatible' ? null : h('div', { className: styles.failReason }, t(`failReason_${k}`)),
263
+ ])
264
+ })(),
254
265
  // 每条失败记录是一个独立卡片:头部(状态/仓库/时间/复制完整日志)→ 修复或提 Issue 动作。
255
266
  // 卡片不展示错误日志预览,完整日志靠「复制完整日志」按钮带走,避免每条卡片被日志撑高
256
267
  !r.ok && (() => {
@@ -334,9 +345,25 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
334
345
  }, t('failIssueBig')) : null,
335
346
  ])
336
347
  }
348
+ if (kind === 'hostIncompatible') {
349
+ // 宿主版本门禁:插件声明的兼容范围不含当前 dsh 版本,安装前就被拒绝。
350
+ // 作者能修(把该版本加进兼容清单),故引导到作者仓库;同时说明本地可临时授权
351
+ const version = /incompatible with dsh\s+([^\s:;,]+)/i.exec(r.message ?? '')?.[1] ?? ''
352
+ return h('div', null, [
353
+ h('div', { className: styles.failReason }, t('failHostIncompatibleReason', { version })),
354
+ h('div', { className: styles.failPrepareHint }, t('failHostIncompatibleHint')),
355
+ r.repo && shownRepo ? h('a', {
356
+ className: styles.failBigIssue,
357
+ href: pluginIssueUrl(shownRepo, r.message, env, r.command, r.attempts, action),
358
+ target: '_blank',
359
+ rel: 'noopener noreferrer',
360
+ title: t('failIssueHint'),
361
+ }, t('failIssueBig')) : null,
362
+ ])
363
+ }
337
364
  if (kind === 'registryVersionStale') {
338
365
  // 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
339
- // 常见于镜像滞后 —— 反查包名走设置里的 npmRegistry,实际安装沿用本机 ~/.npmrc 的源;
366
+ // 常见于镜像滞后 —— 实际安装沿用本机 ~/.npmrc 的源;
340
367
  // 也可能只是版本号写错。与插件作者无关 —— 给对齐源 / 显式走官方源 / 离线包指引,不引导提 Issue
341
368
  return h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
342
369
  }
@@ -367,7 +394,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
367
394
  return h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint', { profile: env?.profile ?? '<profile>' }))
368
395
  }
369
396
  if (kind === 'network') {
370
- // 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
397
+ // 安装前网络探测 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
371
398
  // 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
372
399
  // 不引导提 Issue
373
400
  return h('div', null, [
@@ -384,7 +411,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
384
411
  ])
385
412
  }
386
413
  if (kind === 'pluginPrepare') {
387
- // [packaging](预检/装后校验拦截:git 分发缺产物)与
414
+ // [packaging](装后校验拦截:分发缺产物)与
388
415
  // prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
389
416
  return h('div', null, [
390
417
  h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(r.message) ? t('failPackagingHint') : t('failPrepareHint')),
@@ -395,7 +422,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
395
422
  rel: 'noopener noreferrer',
396
423
  title: t('failIssueHint'),
397
424
  }, t('failIssueBig')) : null,
398
- // 次要入口:这条分支的判据([packaging] 预检/装后校验、prepare 摘要)是插件中心
425
+ // 次要入口:这条分支的判据([packaging] 装后校验、prepare 摘要)是插件中心
399
426
  // 自己产出的,不是 pnpm 原文 —— 判错时用户会被送去作者仓库、我们永远收不到反馈
400
427
  r.repo && shownRepo ? h('div', null, [
401
428
  h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
@@ -433,11 +460,34 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
433
460
  h('div', { className: styles.failPrepareHint }, t('failHostCliSpawnHint')),
434
461
  ])
435
462
  }
436
- // pnpm allowBuilds 拦截与未知失败:不归到插件作者,改为引导到插件中心仓库排查
463
+ if (kind === 'pnpmIgnoredBuild') {
464
+ // 插件的依赖需要在用户机器上编译原生模块,pnpm 默认拦住;我们已自动放行
465
+ // allowBuilds 并重试过一次,仍失败说明任何用户都装不上 —— 属作者侧(发预构建产物 / 走 release 包)
466
+ return h('div', null, [
467
+ h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild')),
468
+ r.repo && shownRepo ? h('a', {
469
+ className: styles.failBigIssue,
470
+ href: pluginIssueUrl(shownRepo, r.message, env, r.command, r.attempts, action),
471
+ target: '_blank',
472
+ rel: 'noopener noreferrer',
473
+ title: t('failIssueHint'),
474
+ }, t('failIssueBig')) : null,
475
+ // 这条判定里也有我们的动作(自动放行过 allowBuilds),判错时得留一条回到插件中心的路
476
+ r.repo && shownRepo ? h('div', null, [
477
+ h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
478
+ h('a', {
479
+ className: styles.failMisjudgeLink,
480
+ href: hubIssueUrl(shownRepo, r.message, env, r.command, r.attempts, 'misjudge', action),
481
+ target: '_blank',
482
+ rel: 'noopener noreferrer',
483
+ title: t('failHubIssueHint'),
484
+ }, t('failMisjudgeLink')),
485
+ ]) : null,
486
+ ])
487
+ }
488
+ // 未知失败:不归到插件作者,改为引导到插件中心仓库排查
437
489
  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 })),
490
+ h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
441
491
  h('a', {
442
492
  className: styles.failBigIssue,
443
493
  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')
@@ -387,22 +385,32 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
387
385
  // 与失败记录一致的布局:类型徽标 + 仓库超链接 + 顶部隐蔽复制按钮
388
386
  h('div', { className: styles.failRow },
389
387
  h('div', { className: styles.failHead },
390
- h('span', {
391
- className: action === 'install' ? styles.failKindInstall : styles.failKindUninstall,
392
- }, actionWord),
393
- // 仓库地址(用户名/仓库)统一显示为可点击超链接,跳转到官网详情页(含插件收录信息),而非直接跳 GitHub
394
- repo ? h('a', {
395
- className: styles.failRepo,
396
- href: pluginSiteUrl(repo),
397
- target: '_blank',
398
- rel: 'noopener noreferrer',
399
- title: repo,
400
- }, repo) : null,
388
+ // 跳官网「插件详情」页;文字里直接给出目标地址(去掉协议),点前就知道会去哪
389
+ repo ? (() => {
390
+ const url = pluginSiteUrl(repo)
391
+ return h('a', {
392
+ className: styles.failRepo,
393
+ href: url,
394
+ target: '_blank',
395
+ rel: 'noopener noreferrer',
396
+ title: url,
397
+ },
398
+ h('span', { className: styles.failRepoLabel }, `${t('detailTitle')} · `),
399
+ h('span', { className: styles.failRepoUrl }, url.replace(/^https?:\/\//, '').split('?')[0]),
400
+ )
401
+ })() : null,
401
402
  // 复制按钮放最上面、浅灰隐蔽;报错正文不允许鼠标选择复制,只能点这里
402
403
  h('button', { className: styles.errorCopySoft, onClick: () => onCopy(copyText) }, t('errorCopy')),
403
404
  ),
404
405
  // 报错信息完整展示(可滚动),比失败记录的预览看得更多
405
406
  h('pre', { className: styles.errorBox }, message),
407
+ // 统一失败卡片头部:报错类型 + 一句话原因(各类型的具体指引仍在下面的分支里)
408
+ h('div', { className: styles.failKindTitle }, t(`failKind_${failureKind}`, failureKind === 'hostIncompatible'
409
+ ? { version: /incompatible with dsh\s+([^\s:;,]+)/i.exec(message)?.[1] ?? '' }
410
+ : undefined)),
411
+ failureKind === 'hostIncompatible'
412
+ ? null
413
+ : h('div', { className: styles.failReason }, t(`failReason_${failureKind}`)),
406
414
  // npm 内部崩溃(edgesOut)+ 本机 npm 版本过低:npm 自身已知缺陷,不是插件问题 ——
407
415
  // 直接给升级指引,不引导提 Issue(提了也是 npm 的问题,插件作者无法修复)
408
416
  failureKind === 'npmTooOld'
@@ -467,9 +475,28 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
467
475
  title: t('failIssueHint'),
468
476
  }, t('failIssueBig')) : null,
469
477
  ])
478
+ : failureKind === 'hostIncompatible'
479
+ // 宿主版本门禁:插件声明的兼容范围不含当前 dsh 版本,安装前就被宿主拒绝。
480
+ // 这是作者能修的(把该版本加进兼容清单),故直接引导到作者仓库;同时说明本地可临时授权
481
+ ? h('div', null, [
482
+ (() => {
483
+ const version = /incompatible with dsh\s+([^\s:;,]+)/i.exec(message)?.[1] ?? ''
484
+ return h('div', null, [
485
+ h('div', { className: styles.failReason }, t('failHostIncompatibleReason', { version })),
486
+ h('div', { className: styles.failPrepareHint }, t('failHostIncompatibleHint')),
487
+ ])
488
+ })(),
489
+ repo ? h('a', {
490
+ className: styles.failBigIssue,
491
+ href: pluginIssueUrl(repo, message, env, command, attempts, action),
492
+ target: '_blank',
493
+ rel: 'noopener noreferrer',
494
+ title: t('failIssueHint'),
495
+ }, t('failIssueBig')) : null,
496
+ ])
470
497
  : failureKind === 'registryVersionStale'
471
498
  // 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
472
- // 常见于镜像滞后 —— 反查包名走设置里的 npmRegistry,实际安装沿用本机 ~/.npmrc 的源,
499
+ // 常见于镜像滞后 —— 实际安装沿用本机 ~/.npmrc 的源,
473
500
  // 官方源刚发布而镜像还没同步;也可能只是版本号写错。与插件作者无关 ——
474
501
  // 给「对齐源 / 那一次显式走官方源 / 用离线包」的指引,不引导提 Issue
475
502
  ? h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
@@ -492,7 +519,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
492
519
  // 安装流程,与插件无关 —— 引导从本机地址重新打开市场,不引导提 Issue
493
520
  ? h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint', { profile: env?.profile ?? '<profile>' }))
494
521
  : failureKind === 'network'
495
- // 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
522
+ // 安装前网络探测 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
496
523
  // 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
497
524
  // 不引导提 Issue
498
525
  ? h('div', null, [
@@ -509,7 +536,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
509
536
  ])
510
537
  : failureKind === 'pluginPrepare'
511
538
  ? h('div', null, [
512
- // [packaging](预检/装后校验拦截:git 分发缺产物)与
539
+ // [packaging](装后校验拦截:分发缺产物)与
513
540
  // prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
514
541
  h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(message) ? t('failPackagingHint') : t('failPrepareHint')),
515
542
  repo ? h('a', {
@@ -519,7 +546,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
519
546
  rel: 'noopener noreferrer',
520
547
  title: t('failIssueHint'),
521
548
  }, t('failIssueBig')) : null,
522
- // 次要入口:这条分支的判据([packaging] 预检/装后校验、prepare 摘要)是插件中心
549
+ // 次要入口:这条分支的判据([packaging] 装后校验、prepare 摘要)是插件中心
523
550
  // 自己产出的,不是 pnpm 原文 —— 判错时用户会被送去作者仓库、我们永远收不到反馈,
524
551
  // 故另给一条「可能是市场误判」的反馈通道(正文自我说明它来自哪条分支)
525
552
  repo ? h('div', null, [
@@ -553,11 +580,33 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
553
580
  // 宿主自身入口起不来:环境问题,给提示、不引导提 Issue
554
581
  h('div', { className: styles.failPrepareHint }, t('failHostCliSpawnHint')),
555
582
  ])
583
+ : failureKind === 'pnpmIgnoredBuild'
584
+ // 该类型的插件依赖需要在用户机器上编译原生模块,pnpm 默认拦住;我们已自动放行
585
+ // allowBuilds 并重试过一次,仍失败说明任何用户都装不上 —— 属作者侧(发预构建产物 / 走 release 包)。
586
+ ? h('div', null, [
587
+ h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild')),
588
+ repo ? h('a', {
589
+ className: styles.failBigIssue,
590
+ href: pluginIssueUrl(repo, message, env, command, attempts, action),
591
+ target: '_blank',
592
+ rel: 'noopener noreferrer',
593
+ title: t('failIssueHint'),
594
+ }, t('failIssueBig')) : null,
595
+ // 这条判定里也有我们的动作(我们自动放行过 allowBuilds),判错时得留一条回到插件中心的路
596
+ repo ? h('div', null, [
597
+ h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
598
+ h('a', {
599
+ className: styles.failMisjudgeLink,
600
+ href: hubIssueUrl(repo, message, env, command, attempts, 'misjudge', action),
601
+ target: '_blank',
602
+ rel: 'noopener noreferrer',
603
+ title: t('failHubIssueHint'),
604
+ }, t('failMisjudgeLink')),
605
+ ]) : null,
606
+ ])
556
607
  : 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 })),
608
+ // 未分类(hub):不归到插件作者,改为引导到插件中心仓库分诊
609
+ h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
561
610
  h('a', {
562
611
  className: styles.failBigIssue,
563
612
  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'