dsh-plugin 1.6.11 → 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 +705 -803
  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 +5 -16
  21. package/lib/services/install/release-target.js +8 -9
  22. package/lib/services/install/task-queue.js +74 -57
  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 +32 -31
  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 +23 -27
  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 +52 -24
  59. package/src/client/components/modals/ProgressView.tsx +3 -4
  60. package/src/client/components/modals/modals.tsx +58 -30
  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 +96 -123
  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 +41 -28
  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 +55 -50
  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 +5 -16
  118. package/src/server/services/install/release-target.ts +8 -9
  119. package/src/server/services/install/task-queue.ts +79 -63
  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
@@ -1,15 +1,3 @@
1
- /**
2
- * 反查 repo(`owner/repo`)对应的官方 npm 包名;未命中、网络异常或超时返回 null。
3
- * 返回 null 不代表仓库一定没有 npm 包,只代表本次未能确认 —— 调用方应保留
4
- * 原有错误路径,反查只是额外的一次尝试。只有「已确认」的结果(包名,或 200
5
- * 响应下确认无匹配)会进缓存;网络异常/超时/解析失败不缓存,同一会话内下次
6
- * 安装仍会重试反查,避免一次瞬时故障把整个会话锁死在 git 通道。
7
- * registry 参数:npm 镜像源地址,空串 = 官方源;与安装通道吃同一 registry,
8
- * 保证「配置了镜像」时反查和安装走同一个源(镜像节点同步完整时结果一致)。
9
- * proxy 参数:GitHub 改名跳转探测走的代理(与安装/诊断同一口径)。
10
- * 慢网络下失败不阻塞安装。
11
- */
12
- export declare function resolveNpmPackage(repo: string, registry?: string, proxy?: string): Promise<string | null>;
13
1
  type NpmSearchPackage = {
14
2
  name?: unknown;
15
3
  keywords?: unknown;
@@ -19,10 +7,10 @@ type NpmSearchPackage = {
19
7
  repository?: unknown;
20
8
  dsh?: unknown;
21
9
  };
22
- type NpmSearchObject = {
23
- package?: NpmSearchPackage;
24
- };
25
10
  export declare function isDshPackageMetadataForRepo(pkg: NpmSearchPackage, repo: string): boolean;
11
+ /**
12
+ * 校验包名对应的 npm 包确实属于该仓库且声明为 DSH 插件:取 registry 上的 `latest`
13
+ * 元数据比对 repository 与插件标记。网络异常、超时、非 200 一律返回 false,不阻塞调用方。
14
+ */
26
15
  export declare function isDshNpmPackageForRepo(packageName: string, repo: string, registry?: string, proxy?: string): Promise<boolean>;
27
- export declare function selectNpmPackageForRepo(objects: NpmSearchObject[], repo: string): string | null;
28
16
  export {};
@@ -4,17 +4,12 @@
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
6
  * 分发文件解析:从 package.json 推出装完后必须真实存在的文件 —— 作者声明的入口
7
- * (exports["."] / main)与 bundle 声明的补丁(dsh.bundle.patch)。git 通道(tar 预检)
8
- * 与 npm 通道(装后 statSync 校验)共用,保证两条通道对同一个包判定一致。
7
+ * (exports["."] / main)与 bundle 声明的补丁(dsh.bundle.patch),供装后校验使用。
9
8
  *
10
- * 这里不替作者补一个 index.js:宿主加载一个 bundle 读的是它的 package.json 和它声明的
11
- * 补丁文件(官方 bundleManifest 的注释即「Read bundle metadata without loading its
12
- * JavaScript」),从不 import 包本身。凭空要求一个作者没声明的入口,只会把完整的
13
- * 技能包判成分发残缺,并把一条假缺陷推到作者仓库(见 QingYunA/answer-me-with-html#209)。
14
- *
15
- * 例外是补丁自己 mount 了本包:dsh 的《打包与安装插件》里组合包的常规写法就是
16
- * `- insert: - name: <本包名>`,宿主随后按包名 import 本包,入口那时必须存在。
17
- * 所以只有补丁真的写了自己的包名,才把这个入口算进分发要求。
9
+ * 只在包自己声明了入口时才要求它存在:宿主加载 bundle 读的是它的 package.json 与声明的补丁,
10
+ * 从不 import 包本身。硬要一个作者没声明过的 index.js,会把完整的包判成分发残缺、把一条假缺陷
11
+ * 推到作者仓库。例外是补丁自己 mount 了本包(组合包的常规写法)—— 那时宿主按包名 import 本包,
12
+ * 入口必须存在,所以只有补丁真的写了自己的包名才把这个入口算进分发要求。
18
13
  */
19
14
  /** 解析所需的 manifest 字段;其余字段不读。 */
20
15
  export interface PackageManifest {
@@ -36,7 +31,7 @@ export declare function resolvePackageEntry(meta: PackageManifest): string | nul
36
31
  /**
37
32
  * 解析 `dsh.bundle.patch` 声明的补丁文件(相对包根,不带 `./` 前缀)。
38
33
  * 形状不合规范(既不是字符串也不是字符串数组)在这里不判定:那是 dsh 启动时
39
- * bundlePatchFiles 的报错职责,预检只负责「声明了就要在包里存在」。
34
+ * bundlePatchFiles 的报错职责,装后校验只负责「声明了就要在包里存在」。
40
35
  */
41
36
  export declare function resolveBundlePatchFiles(meta: PackageManifest): string[];
42
37
  /** 分发里必须存在的全部文件:作者声明的入口,加上 bundle 声明的补丁,入口在前。
@@ -3,14 +3,13 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * 预构建 GitHub release 安装目标(目录权威命令 → 可安装直链)。
7
- * 部分插件(如 loopx)把构建产物发布成 GitHub release 的 .tgz,仓库 HEAD 反而是
8
- * monorepo 根、走 git 直装会因缺入口文件失败;目录下发的权威安装命令直接指向该
9
- * release 包。这里把「目录命令」解析成安全的 release 目标,供客户端选路与服务端
10
- * 校验共用(单一事实来源,避免两端口径漂移)。
6
+ * 预构建 release 直链的仓库身份解析。部分插件(如 loopx)当初以 GitHub release 的 .tgz
7
+ * 安装过,仓库 HEAD 反而是 monorepo 根、走 git 直装会因缺入口文件失败;老安装记录里留的
8
+ * 就是这种 release 直链。这里把「历史安装目标」还原成所属仓库身份,供已装态读回、队列
9
+ * 去重与展示复用(单一事实来源,避免两端口径漂移)。
11
10
  *
12
11
  * 只接受固定形态的 HTTPS `github.com/<owner>/<repo>/releases/download/<tag>/<asset>.tgz`:
13
- * 目录命令是展示数据、绝非 shell 代码,绝不执行命令文本;跨仓跳转、外部主机、凭据、
12
+ * 输入是记录/展示数据、绝非 shell 代码,绝不执行其文本;跨仓跳转、外部主机、凭据、
14
13
  * 移动的 latest 链接、query/fragment、多余 CLI 参数与 shell 片段一律拒绝。
15
14
  */
16
15
  /** release 包目标及其所属仓库身份(owner/repo);非法返回 null。 */
@@ -21,8 +20,8 @@ export interface GithubReleaseTarget {
21
20
  repo: string;
22
21
  }
23
22
  /**
24
- * 解析一个 release 安装目标。
25
- * @param value - 目录命令(`dsh plugin --profile web add <url>`)或裸直链。
23
+ * 解析一个历史安装目标里的 release 直链。
24
+ * @param value - 完整命令(`dsh plugin --profile web add <url>`)或裸直链。
26
25
  * @returns 命中返回 `{ target, repo }`,否则 null。
27
26
  */
28
27
  export declare function githubReleaseTarget(value: string): GithubReleaseTarget | null;
@@ -1,6 +1,6 @@
1
- import type { InstallResult, InstallTask } from './install-types.ts';
1
+ import type { InstallResult, InstallTask, Invocation } from './install-types.ts';
2
2
  import type { LoaderHandle } from '../loader.ts';
3
- /** Snapshot of a task (keeps the live object untouched by consumers). */
3
+ /** 任务快照(避免消费方改动实时对象)。 */
4
4
  export declare function getTask(id: number): InstallTask | undefined;
5
5
  export interface ActiveTaskInfo {
6
6
  id: number;
@@ -9,28 +9,26 @@ export interface ActiveTaskInfo {
9
9
  status: 'pending' | 'running';
10
10
  progress: number;
11
11
  lines: string[];
12
- /** 尝试过的安装方式(npm 反查 + 实际执行命令):失败时前端 issue 预填需要 */
12
+ /** 尝试过的安装方式(实际执行过的命令):失败时前端 issue 预填需要 */
13
13
  attempts: string[];
14
- /** 自定义安装入口渠道(NPM 包 / GitHub 源码 / DSH 命令卡片):前端刷新恢复后仍能精准溯源 */
15
- installChannel?: 'npm' | 'git' | 'dsh';
14
+ /** 自定义安装入口渠道(NPM 包卡片):前端刷新恢复后仍能精准溯源 */
15
+ installChannel?: 'npm';
16
16
  }
17
- /** All non-terminal tasks in queue order (running first, then pending). Lets the client resume a queue after a page reload. */
17
+ /** 全部未终态任务,按队列顺序(running 在前、pending 在后)。供客户端刷新页面后恢复队列。 */
18
18
  export declare function activeTask(): ActiveTaskInfo[];
19
19
  /**
20
- * True while a mutation child process is still alive. The concurrent slot is
21
- * keyed off the live child set rather than task.status: cancelling a running
22
- * task flips its status immediately, but the child may still be shutting down
23
- * (the close event lands later) — the slot must stay held until then, otherwise
24
- * a new task would start while the old pnpm still runs in the same profile dir.
20
+ * 变更子进程仍存活时为 true。并发占位以存活子进程集合为准,而非 task.status:
21
+ * 取消运行中任务会立即翻转其状态,但子进程可能仍在退出中(close 事件稍后才到)——
22
+ * 占位必须维持到那时,否则旧 pnpm 还在同一 profile 目录跑,新任务就开始了。
25
23
  */
26
24
  export declare function hasRunningTask(): boolean;
27
- /** Whether the target already has a non-terminal task in the queue (install/uninstall dedupe). */
25
+ /** 目标在队列里是否已有未终态任务(安装/卸载去重)。 */
28
26
  export declare function hasQueuedTarget(target: string): boolean;
29
27
  /**
30
- * Cancel a queued or running task.
31
- * - pending: removed from the queue immediately (never spawned).
32
- * - running: kills the child process; the queue worker then picks the next one.
33
- * Returns false when the task is unknown or already finished.
28
+ * 取消排队中或运行中的任务。
29
+ * - pending:立即移出队列(从未 spawn)。
30
+ * - running:杀掉子进程;队列 worker 随后取下一个。
31
+ * 任务不存在或已结束时返回 false。
34
32
  */
35
33
  export declare function cancelTask(id: number): boolean;
36
34
  /**
@@ -41,9 +39,8 @@ export declare function cancelTask(id: number): boolean;
41
39
  */
42
40
  export declare function desktopCliBootstrap(entry: string): string | null;
43
41
  /**
44
- * Enqueue a plugin mutation and return its task. Tasks run strictly serially:
45
- * the queue worker starts the next one only after the previous finishes.
46
- * Progress is visible through `getTask(id)` / `activeTask()` until done.
42
+ * 插件变更入队并返回其任务。任务严格串行:队列 worker 只在上一个结束后才启动下一个。
43
+ * 完成前可通过 `getTask(id)` / `activeTask()` 查看进度。
47
44
  */
48
45
  export declare function startPluginMutation(options: {
49
46
  action: 'add' | 'remove' | 'update';
@@ -58,17 +55,17 @@ export declare function startPluginMutation(options: {
58
55
  /** 运行中 loader:安装成功后热挂新条目、卸载成功后停用旧条目,两类操作都尽量立即生效
59
56
  * (缺失时安装/卸载仍需重启) */
60
57
  loader?: LoaderHandle;
61
- /** 入队前已尝试的安装方式(npm registry 反查等):失败提 Issue 时如实展示;实际执行的命令由 spawn 时追加 */
58
+ /** 入队前已尝试的安装方式:失败提 Issue 时如实展示;实际执行的命令由 spawn 时追加 */
62
59
  attempts?: string[];
63
60
  /** 更新已安装的 npm 包(mode=update):命令显式带 @latest,让 pnpm 真正解析最新版本 */
64
61
  updateNpm?: boolean;
65
- /** 自定义安装入口渠道(客户端三张卡片:NPM 包 / GitHub 源码 / DSH 命令):
66
- * 任务输出行与系统日志据此溯源「从哪个入口发起」;目录插件安装不传 */
67
- installChannel?: 'npm' | 'git' | 'dsh';
62
+ /** 自定义安装入口渠道(NPM 包卡片):任务输出行与系统日志据此溯源「从哪个入口发起」;
63
+ * 目录插件安装不传 */
64
+ installChannel?: 'npm';
68
65
  }): InstallTask;
69
66
  /**
70
67
  * 安装后校验已装包里 package.json 声明的文件(main / exports["."].default 与
71
- * dsh.bundle.patch)是否真实存在。git: 分发常不提交构建产物(lib/ 等),pnpm 装完没有
68
+ * dsh.bundle.patch)是否真实存在。npm 包也可能漏发构建产物(lib/ 等),pnpm 装完没有
72
69
  * 报错,但宿主重启加载插件树时会 ERR_MODULE_NOT_FOUND 直接崩溃、网页打不开 —— 这里在
73
70
  * 登记「待重启」前就把这类残缺包拦下。没有声明过的文件不在这里追究:没声明入口的
74
71
  * bundle 包(只挂技能)本来就不需要入口,宿主读的是它的 package.json 与声明的补丁。
@@ -79,13 +76,17 @@ export declare function verifyInstalledEntry(profile: string, target: string): {
79
76
  missing: string | null;
80
77
  };
81
78
  /**
82
- * Run a plugin mutation with one recovery path: when `add` fails because
83
- * pnpm's allowBuilds gate blocks a build script — either the git-hosted
84
- * package's own `prepare` (`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, key from
85
- * the printed key@url hint) or a dependency's native build
86
- * (`ERR_PNPM_IGNORED_BUILDS`, key = the listed name@version) — write the
87
- * exact keys into the profile's pnpm-workspace.yaml and retry once.
88
- * Other failures fall through to the single-spawn result.
79
+ * 宿主自身 CLI 调不起来时的 ASCII 标记行:spawn 我们解析出的宿主入口报 ENOENT 时返回
80
+ * `[host-spawn]`,供前端单独归因、不引导提 Issue。PATH 上的 dsh/npm 各有自己的标记,返回 null。
81
+ * 导出供单测覆盖判据。
82
+ */
83
+ export declare function hostSpawnMarker(invocation: Invocation, error: unknown): string | null;
84
+ /**
85
+ * 执行插件变更,带一条恢复路径:当 `add` 因 pnpm 的 allowBuilds 门禁拦下构建脚本而失败 ——
86
+ * 或是 git 源包自己的 `prepare`(`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`,key 取自打印的
87
+ * key@url 提示),或是某依赖的原生构建(`ERR_PNPM_IGNORED_BUILDS`,key = 列出的 name@version)——
88
+ * 就把确切的 key 写进 profile 的 pnpm-workspace.yaml 并重试一次。
89
+ * 其它失败一律落到单次 spawn 的结果。
89
90
  */
90
91
  export declare function runPluginMutation(options: {
91
92
  action: 'add' | 'remove' | 'update';
@@ -0,0 +1,15 @@
1
+ export interface NpmRelease {
2
+ version: string;
3
+ publishedAt: string;
4
+ }
5
+ /** 请求体包名数量上限:只为已安装插件按需查询,几十个足矣,封顶防滥用。 */
6
+ export declare const MAX_RELEASE_AGE_PACKAGES = 50;
7
+ /** 单包查询超时(ms):慢网络下失败即返回 null,不拖住整个响应。 */
8
+ export declare const RELEASE_AGE_TIMEOUT_MS = 8000;
9
+ export declare const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
10
+ /** 从完整 packument 取 dist-tags.latest 对应版本及其发布时间;缺任一字段返回 null。 */
11
+ export declare function latestReleaseOf(packument: unknown): NpmRelease | null;
12
+ /** 请求体 → 去重后的合法包名列表;非数组 / 超量 / 含非法包名一律 null(拒绝非法输入)。 */
13
+ export declare function parseReleaseAgeRequest(body: unknown): string[] | null;
14
+ /** 查询一个包的最新版本与发布时间(成功结果缓存,同会话内同包不重复请求);registry 为空回退官方源,失败返回 null。 */
15
+ export declare function lookupReleaseAge(packageName: string, registry: string, proxy: string, timeoutMs?: number): Promise<NpmRelease | null>;
@@ -33,7 +33,7 @@ export declare function tlsRevocationBlocked(stderr: string): boolean;
33
33
  * `http=host:port;https=host:port`(按协议分别配置)。
34
34
  * 字符类必须同时排除 `/` 与 `=`:只排除 `=` 时,带 scheme 的值会让「主机名」吞掉 `//`,
35
35
  * 拼出 `http:////127.0.0.1:10793` 这种立刻失败的非法地址 —— 代理本来是通的,却把诊断、
36
- * 目录拉取、npm/git 预检全判成「不可达」。
36
+ * 目录拉取、安装前网络探测全判成「不可达」。
37
37
  */
38
38
  export declare function parseWinProxyServer(stdout: string): string | null;
39
39
  /**
@@ -49,13 +49,13 @@ export declare function systemProxy(): string | null;
49
49
  */
50
50
  export declare function probeUrl(url: string, proxy: string, timeoutMs: number): Promise<ProbeResult>;
51
51
  /**
52
- * git 通道真实克隆握手探测:spawn git ls-remote(https 传输,与 pnpm 克隆前的
52
+ * git 真实克隆握手探测:spawn git ls-remote(https 传输,与 pnpm 克隆前的
53
53
  * ref 握手一致),注入代理 env,GIT_TERMINAL_PROMPT=0 防凭据提示挂起。
54
54
  *
55
55
  * 为什么要真实 git 而非 curl 打网页:网页「能打开」和 git「能克隆」是两码事 ——
56
56
  * 防火墙/代理常按端口与协议区分,HTTP 页可达不代表 git 传输可达。这里测的就是
57
- * 克隆握手本身,回答「github:owner/repo 装不装得动」。
58
- * 供系统诊断 GitHub 通道使用;探测目标用 dshplugin/hello-dsh 小仓库(秒级完成,
57
+ * 克隆握手本身,回答「能不能连上 github.com」。
58
+ * 供系统诊断 GitHub 可达性使用;探测目标用 dshplugin/hello-dsh 小仓库(秒级完成,
59
59
  * 不打 17MB 的 dsh-plugin-hub 主页)。
60
60
  */
61
61
  export declare function gitLsRemote(url: string, proxy: string, timeoutMs: number): Promise<ProbeResult>;
@@ -4,21 +4,19 @@ export interface InstalledVersionRecord {
4
4
  /** 安装时的仓库最近更新时间(ISO,GitHub pushed_at);无 release 仓库的更新依据 */
5
5
  updatedAt: string;
6
6
  installedAt: string;
7
- /** npm 优先通道反查命中的官方 npm 包名(repo → 包名映射):目录数据可能未下发
7
+ /** 安装时记录在案的官方 npm 包名(repo → 包名映射):目录数据可能未下发
8
8
  * npmPackage(组织 scope 与 GitHub 用户名不一致),客户端据此把依赖 key 匹配回仓库 */
9
9
  npmPackage?: string;
10
10
  }
11
11
  export type InstalledVersionTable = Record<string, InstalledVersionRecord>;
12
12
  export declare function versionsFilePath(profile: string): string;
13
13
  export declare function readInstalledVersions(profile: string): InstalledVersionTable;
14
- /** Record the catalog signals (version + repo updated time) present at install time for one repo. */
14
+ /** 记录某仓库安装时的目录信号(版本 + 仓库更新时间)。 */
15
15
  export declare function recordInstalledVersion(profile: string, repo: string, version: string, updatedAt: string): void;
16
16
  /**
17
- * Record the npm package name resolved for a repo (npm-first install channel).
18
- * Called at `/install` time so the mapping exists even before the task finishes;
19
- * harmless when the repo is not (yet) installed — the client only treats a repo
20
- * as installed when its dependency key actually exists.
17
+ * 记录某仓库解析出的 npm 包名。在 `/install` 时调用,让映射在任务完成前就存在;
18
+ * 仓库尚未安装时也无害 —— 客户端只在依赖 key 真实存在时才把仓库当作已安装。
21
19
  */
22
20
  export declare function recordResolvedNpmPackage(profile: string, repo: string, npmPackage: string): void;
23
- /** Forget the recorded version for one repo (called on uninstall). */
21
+ /** 清除某仓库记录的版本(卸载时调用)。 */
24
22
  export declare function removeInstalledVersion(profile: string, repo: string): void;
@@ -1,4 +1,4 @@
1
- /** Resolve the active profile from the booted CLI args, falling back to `web`. */
1
+ /** 从启动参数解析当前 profile,取不到落回 `web`。 */
2
2
  export declare function readProfileArg(fallback?: string): string;
3
3
  /**
4
4
  * 从启动参数解析当前 profile,两种宿主形态都认:
@@ -10,32 +10,28 @@ export declare function readProfileArg(fallback?: string): string;
10
10
  * 位置参数按「父目录名为 profiles」精确识别 profile 目录,避免误取其它位置参数。
11
11
  */
12
12
  export declare function profileFromArgv(argv: readonly string[], fallback?: string): string;
13
- /** Resolve a profile directory (`DSH_HOME` or `~/.dsh`). */
13
+ /** 解析 profile 目录(`DSH_HOME` 或 `~/.dsh`)。 */
14
14
  export declare function profileDirectory(profile: string): string;
15
- /** Build a safe explicit HTTPS Git target, or null when the repo is unsafe.
16
- * When `ref` is given it is appended as a Git ref (`#tag` / `#commit`) so an
17
- * explicitly pinned revision reaches the installer instead of being dropped. */
15
+ /** 把仓库身份转成规范的 GitHub 目标(git+https),仓库不合法时返回 null。
16
+ * 带 ref 时按 Git ref(`#tag` / `#commit`)拼接;安装通道已不再使用 git 目标,此函数仅做身份规范化。 */
18
17
  export declare function githubTarget(repo: string, ref?: string | null): string | null;
19
- export declare function githubRefOf(value: string): string | null;
20
18
  export declare function installTargetOf(value: string): string;
21
19
  export declare function globalNpmPackagesOf(value: string): string[] | null;
22
- /** Extract an owner/repo identity from a catalog value or an installed Git spec. */
20
+ /** 从目录值或已装 Git spec 中提取 owner/repo 身份。 */
23
21
  export declare function githubRepoOf(value: string): string | null;
24
- /** Validate an npm package name (uninstall target grammar). */
22
+ /** 校验 npm 包名(卸载目标的语法)。 */
25
23
  export declare function validPackageName(name: string): boolean;
26
24
  /**
27
- * The exact `allowBuilds` key pnpm printed in its
28
- * `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` hint. For Git installs pnpm
29
- * matches against `name@<resolved fetch URL>` — the URL it actually fetches,
30
- * e.g. `https://codeload.github.com/<owner>/<repo>/tar.gz/<commit>` — not the
31
- * `git+ssh://`/branch spec. A bare name or a `git+` spec does not match, so
32
- * the only reliable key is the one pnpm prints verbatim. Null when absent.
25
+ * 取 pnpm 在 `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` 提示里原样打印的 `allowBuilds` key。
26
+ * Git 安装时 pnpm 按 `name@<实际 fetch URL>`(如
27
+ * `https://codeload.github.com/<owner>/<repo>/tar.gz/<commit>`)匹配,而不是 `git+ssh://`
28
+ * 或分支 spec —— 裸包名与 `git+` spec 都匹配不上,唯一可靠的 key 就是 pnpm 原样输出的那个。
29
+ * 无则返回 null。
33
30
  */
34
31
  export declare function parseAllowBuildsKey(output: string): string | null;
35
32
  /**
36
- * Add one `allowBuilds` entry to the profile's pnpm-workspace.yaml, merging
37
- * with existing entries and leaving the rest of the yaml intact. Broken
38
- * placeholder entries (pnpm's failed-install bug writes a literal
39
- * `set this to true or false` value) are dropped on rewrite.
33
+ * 向 profile 的 pnpm-workspace.yaml 追加一条 `allowBuilds` 记录:与已有条目合并,
34
+ * 其余 yaml 保持不变;重写时丢弃损坏的占位条目(pnpm 安装失败的 bug 会写入
35
+ * 字面量 `set this to true or false`)。
40
36
  */
41
37
  export declare function addAllowBuildsKey(profile: string, key: string): void;
@@ -3,19 +3,16 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Pure progress-estimation helpers shared by the install runner.
6
+ * 安装执行器共用的纯进度估算工具。
7
7
  *
8
- * The dsh CLI wraps pnpm, which refreshes its `Progress: …` line in place
9
- * with carriage returns, so raw output arrives as a stream of partial
10
- * fragments. These helpers normalize one fragment into a clean terminal
11
- * line and map it onto a 0-100 estimate; keeping them free of I/O makes the
12
- * estimation logic directly unit-testable (see tests/progress.test.ts).
8
+ * dsh CLI 包装了 pnpm,pnpm 用回车原地刷新 `Progress: …` 行,因此原始输出是一串碎片。
9
+ * 这些工具把单个碎片归一化成干净的终端行,并映射到 0-100 估算值;不碰 I/O
10
+ * 让估算逻辑可直接单测(见 tests/progress.test.ts)。
13
11
  */
14
- /** Strip the `[out]` / `[err]` source prefix and ANSI escape sequences. */
12
+ /** 剥掉 `[out]` / `[err]` 来源前缀与 ANSI 转义序列。 */
15
13
  export declare function cleanLine(raw: string): string;
16
14
  /**
17
- * Estimate 0-100 progress from one CLI output line. pnpm emits
18
- * `Progress: resolved N, reused X, downloaded Y, added Z` during the fetch
19
- * phase; resolution/install phase lines bump the estimate towards done.
15
+ * 从单行 CLI 输出估算 0-100 进度。pnpm 在 fetch 阶段输出
16
+ * `Progress: resolved N, reused X, downloaded Y, added Z`;解析/安装阶段的行把估算值推向完成。
20
17
  */
21
18
  export declare function estimateProgress(line: string): number;
@@ -7,10 +7,6 @@ export interface HubSettings {
7
7
  npmRegistry: string;
8
8
  /** 启用命令行安装 NPM 包(默认开;关掉后输入 npm 包名安装被拦截) */
9
9
  enableNpmInstall: boolean;
10
- /** 启用命令行安装 GitHub 源码(默认开;关掉后输入 GitHub 地址安装被拦截) */
11
- enableGitInstall: boolean;
12
- /** 启用命令行粘贴 dsh plugin 命令安装(默认开;关掉后命令输入被禁用) */
13
- enableDshInstall: boolean;
14
10
  /** 日志存放位置覆盖(空串 = 默认 ~/.dsh/profiles/<profile>/hub.log;可填目录或 .log 文件路径) */
15
11
  logPath: string;
16
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin",
3
- "version": "1.6.11",
3
+ "version": "2.0.0",
4
4
  "description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 10000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):10000+ 人工精选社区插件,每日更新。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -3,10 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Plugin Hub section: wires the catalog data pipeline, the background task
7
- * queue and the feedback state together. Data/queue logic lives in hooks/,
8
- * rendering is delegated to the small presentational components in this
9
- * folder; it hosts the dialogs/toast and the section-level copy actions.
6
+ * 插件中心板块:把目录数据管道、后台任务队列与反馈状态接在一起。数据/队列逻辑在 hooks/,
7
+ * 渲染交给本目录下的小型展示组件;本文件托管各弹窗/Toast 与板块级复制动作。
10
8
  */
11
9
  import { createElement as h, useEffect, useRef, useState } from 'react'
12
10
  import type { MouseEvent } from 'react'
@@ -27,7 +25,7 @@ import { HubUpdateModal } from './modals/HubUpdateModal.tsx'
27
25
  import { InstalledDetailModal } from './modals/InstalledDetailModal.tsx'
28
26
  import { NotificationsModal } from './modals/NotificationsModal.tsx'
29
27
  import { addFailure, addSuccess, addUpdateNotice, clearNotifications, ignoreUpdate, loadIgnoredUpdates, loadNotifications, removeNotification, removeUpdateNotice } from '../logic/failures.ts'
30
- import type { NotificationRecord } from '../logic/failures.ts'
28
+ import type { FailureAction, NotificationRecord } from '../logic/failures.ts'
31
29
  import { CatalogHeader } from './layout/CatalogHeader.tsx'
32
30
  import { MarketView } from './views/MarketView.tsx'
33
31
  import { SectionTabs } from './layout/SectionTabs.tsx'
@@ -78,10 +76,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
78
76
  const [confirmPlugin, setConfirmPlugin] = useState<HubPlugin | null>(null)
79
77
  /** 弹窗动作是否为「更新」:已安装插件点「更新」→ 走同一条 add 命令原位覆盖重装,文案区分安装/更新 */
80
78
  const [confirmIsUpdate, setConfirmIsUpdate] = useState(false)
81
- /** 命令行安装确认弹窗:记录待安装的裸目标(npm 包名 / GitHub 地址,DSH 命令已剥成裸目标)——
79
+ /** 命令行安装确认弹窗:记录待安装的裸目标(npm 包名)——
82
80
  * 与应用商店同一套确认/进度/结果弹窗,提供时 plugin 传 null 走 customTarget 模式 */
83
81
  const [confirmCustomTarget, setConfirmCustomTarget] = useState<string | null>(null)
84
- /** 自定义安装入口渠道(三张卡片之一):随确认弹窗记录,提交时上报服务端,
82
+ /** 自定义安装入口渠道(NPM 包卡片):随确认弹窗记录,提交时上报服务端,
85
83
  * 报错/日志据此精准溯源「从哪个入口发起」(npm 全局安装固定为 'npm') */
86
84
  const [confirmCustomChannel, setConfirmCustomChannel] = useState<InstallChannel | null>(null)
87
85
  /** 全局 npm 安装确认弹窗(官方 `npm install -g <pkgs>`):记录原始命令 + 解析出的包列表,
@@ -114,8 +112,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
114
112
  const [uninstallNeedsReload, setUninstallNeedsReload] = useState(false)
115
113
  /** 结果视图「立即重启」:请求宿主重启后进入等待,服务回来后整页刷新 */
116
114
  const [restarting, setRestarting] = useState(false)
117
- /** 操作失败完整信息 + 所属插件仓库 + 失败类型(决定弹窗标题「安装失败/卸载失败」)+ 实际执行的安装命令 + 尝试过的安装方式(issue 预填用) */
118
- const [errorMsg, setErrorMsg] = useState<{ message: string; repo: string | null; kind: 'install' | 'uninstall'; command?: string; attempts?: string[] } | null>(null)
115
+ /** 操作失败信息 + 归属仓库 + 动作(决定弹窗标题与上报措辞)+ 命令与尝试记录(issue 预填用) */
116
+ const [errorMsg, setErrorMsg] = useState<{ message: string; repo: string | null; action: FailureAction; command?: string; attempts?: string[] } | null>(null)
119
117
  /** 通知中心记录:localStorage 持久化,每次安装/卸载任务成败都留痕(启动时读回) */
120
118
  const [notifications, setNotifications] = useState<NotificationRecord[]>(() => loadNotifications())
121
119
  /** 通知中心弹窗:一级导航「设置」tab 后边的铃铛入口按钮打开 */
@@ -142,7 +140,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
142
140
 
143
141
  const queue = useTaskQueue({
144
142
  t,
145
- // 界面语言带给服务端:网络预检等错误消息按用户语言提示(中文界面给中文,英文界面给英文)
143
+ // 界面语言带给服务端:安装前网络探测等错误消息按用户语言提示(中文界面给中文,英文界面给英文)
146
144
  langKey,
147
145
  refreshInstalled: catalog.refreshInstalled,
148
146
  onInstallDone: (viaModal, repo, needsRestart, update, needsReload) => {
@@ -173,7 +171,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
173
171
  // 提 Issue 目标先反查仓库身份:npm 包名能在已安装表/目录里反查到 owner/repo 才保留,
174
172
  // 反查不到置 null —— 错误弹窗据此不显示「一键提 Issue」按钮(防提到错误仓库)
175
173
  const issueRepo = issueRepoOf(repo ?? '', catalog.installedItems, catalog.plugins)
176
- setErrorMsg({ message, repo: issueRepo, kind, command, attempts })
174
+ // 动作:更新失败要显示「更新失败」,其余按安装/卸载
175
+ setErrorMsg({ message, repo: issueRepo, action: update ? 'update' : kind, command, attempts })
177
176
  // 失败自动写入通知记录:任务在后台结束时没人盯着也能留痕。
178
177
  // 存原始 repo(可能只是裸包名)而非反查结果 —— 反查只服务于提 Issue,
179
178
  // 通知中心的展示反查由 NotificationsModal 做、查不到会回退显示原始值,
@@ -223,10 +222,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
223
222
  useEffect(() => {
224
223
  if (startupCheckedRef.current) return
225
224
  if (!settingsReady || catalog.plugins === null || catalog.failed) return
225
+ // 供应链门禁结果未就绪时先等:否则刚发布的插件会先写进通知中心,冒出装不上的更新提醒
226
+ if (!catalog.releaseAgeReady) return
226
227
  startupCheckedRef.current = true
227
228
  if (!hubSettings.checkUpdatesOnStart) return
228
229
  syncUpdateNotices()
229
- }, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, queue])
230
+ }, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, catalog.releaseAgeReady, queue])
230
231
 
231
232
  // 信任/卸载/更新说明弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
232
233
  useEffect(() => {
@@ -264,8 +265,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
264
265
  await navigator.clipboard.writeText(text)
265
266
  return true
266
267
  } catch {
267
- // Clipboard API unavailable (permissions/iframe) — fall back to the
268
- // legacy hidden-textarea trick, which works in any context.
268
+ // Clipboard API 不可用(权限/iframe)—— 退回旧的隐藏 textarea 方案,任何环境都可用。
269
269
  const ta = document.createElement('textarea')
270
270
  ta.value = text
271
271
  ta.style.position = 'fixed'
@@ -305,7 +305,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
305
305
  Object.keys(catalog.installed).some((k) => k.toLowerCase() === pkg)
306
306
  }
307
307
 
308
- /** 弹窗动作一:复制安装命令到剪贴板,引导去终端粘贴执行(npm 通道显示包名命令)。 */
308
+ /** 弹窗动作一:复制安装命令到剪贴板,引导去终端粘贴执行(npm 安装显示包名命令)。 */
309
309
  const copyCommand = async (p: HubPlugin) => {
310
310
  const repo = p.source?.repo ?? ''
311
311
  const ok = await doCopy(installCommandOf(p))
@@ -416,8 +416,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
416
416
  noticeCount: notifications.length + queue.queue.length + queue.pendingRestarts.length,
417
417
  onOpenNotifications: () => {
418
418
  // 打开弹窗时补齐更新提醒(受「启动时检查更新」开关约束):同一版本只一条,
419
- // 清空/删除后重开仍恢复,直到插件更新完成才消失
420
- if (hubSettings.checkUpdatesOnStart) syncUpdateNotices()
419
+ // 清空/删除后重开仍恢复,直到插件更新完成才消失;门禁结果未就绪时先不写,避免提示装不上的更新
420
+ if (hubSettings.checkUpdatesOnStart && catalog.releaseAgeReady) syncUpdateNotices()
421
421
  setShowNotifications(true)
422
422
  },
423
423
  }),
@@ -488,17 +488,13 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
488
488
  : view === 'custom'
489
489
  ? h(CustomInstallView, {
490
490
  t,
491
- // 命令行安装:与应用商店同一套确认/进度/结果弹窗 —— 输入 npm 包名 / GitHub 地址 /
492
- // dsh plugin 命令即装(custom 源,受安全设置三开关控制)。点安装先弹确认窗,
491
+ // 命令行安装:与应用商店同一套确认/进度/结果弹窗 —— 输入 npm 包名 / npm、pnpm
492
+ // 安装命令即装(custom 源,受安全设置开关控制)。点安装先弹确认窗,
493
493
  // 确认后走队列安装(实时进度 + 成功结果视图);目标已安装 → 弹窗转「更新」覆盖重装,
494
494
  // 不会撞「already installed」报错。
495
- // 安全信任开关关掉对应通道 → 卡片禁用并提示去设置打开(onOpenSettings 跳到设置页)。
495
+ // 安全信任开关关掉 → 卡片禁用并提示去设置打开(onOpenSettings 跳到设置页)。
496
496
  enableNpm: hubSettings.enableNpmInstall,
497
- enableGit: hubSettings.enableGitInstall,
498
- enableDsh: hubSettings.enableDshInstall,
499
497
  onOpenSettings: () => setView('settings'),
500
- // 当前 profile:一键插入的 Hub 更新命令用它拼 --profile
501
- profile: env?.profile ?? 'web',
502
498
  onInstallCustom: (raw, opts) => {
503
499
  const target = raw.trim()
504
500
  if (!target) return
@@ -515,7 +511,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
515
511
  return
516
512
  }
517
513
  setConfirmGlobalNpm(null)
518
- // 记录入口渠道:报错/日志据此精准提示「从哪张卡片发起的安装」(NPM 包 / GitHub 源码 / DSH 命令)
514
+ // 记录入口渠道:报错/日志据此精准提示「从哪张卡片发起的安装」
519
515
  setConfirmCustomChannel(opts.installChannel)
520
516
  setConfirmIsUpdate(customTargetInstalled(target))
521
517
  setConfirmCustomTarget(target)
@@ -695,7 +691,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
695
691
  },
696
692
  onInstall: () => void queue.installCustom(confirmCustomTarget, {
697
693
  update: confirmIsUpdate,
698
- // 入口渠道(NPM 包 / GitHub 源码 / DSH 命令卡片):上报服务端,报错/日志据此溯源
694
+ // 入口渠道(NPM 包卡片):上报服务端,报错/日志据此溯源
699
695
  channel: confirmCustomChannel ?? 'npm',
700
696
  }),
701
697
  onRestart: () => setShowRestartConfirm(true),
@@ -793,7 +789,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
793
789
  errorMsg && h(ErrorModal, {
794
790
  message: errorMsg.message,
795
791
  repo: errorMsg.repo,
796
- kind: errorMsg.kind,
792
+ action: errorMsg.action,
797
793
  command: errorMsg.command,
798
794
  attempts: errorMsg.attempts,
799
795
  t,
@@ -3,10 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Catalog list: the scrollable container with the loading / failed / empty
7
- * states, the plugin cards, and the count footer. Resets scroll position when
8
- * the category or install-status filter changes so the replaced content is
9
- * not mistaken for a no-op update.
6
+ * 目录列表:承载加载/失败/空三态、插件卡片与计数页脚的滚动容器。分类或安装状态
7
+ * 筛选变化时重置滚动位置,避免替换后的内容被误当成「没变化」。
10
8
  */
11
9
  import { createElement as h, useEffect, useRef } from 'react'
12
10
  import styles from '../../styles/List.module.css'
@@ -3,9 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * One plugin card in the catalog list: name/category/verified badge,
7
- * description + topics, star/fork/date stats, and the detail/install/
8
- * uninstall actions.
6
+ * 目录列表里的单张插件卡片:名称/分类/verified 徽标、描述 + 标签、Star/Fork/日期
7
+ * 统计,以及详情/安装/卸载动作。
9
8
  */
10
9
  import { createElement as h } from 'react'
11
10
  import styles from '../../styles/List.module.css'
@@ -3,12 +3,11 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Toolbar above the market list:
7
- * row 1 — full-width search input;
8
- * row 2 — result count on the left, sort segmented control on the right
9
- * (mirrors the Installed view: clicking the same sort toggles
10
- * ascending/descending, a new sort uses its default order; the active
11
- * button shows an up/down arrow for the current direction).
6
+ * 市场列表上方的工具栏:
7
+ * 第一行 —— 整行宽的搜索框;
8
+ * 第二行 —— 左侧结果计数,右侧排序分段控件
9
+ * (与已安装视图一致:点同一排序切换正/倒序,点新排序用其默认方向;
10
+ * 激活按钮显示当前方向的上/下箭头)。
12
11
  */
13
12
  import { createElement as h, Fragment } from 'react'
14
13
  import type { FormEvent, ReactNode } from 'react'
@@ -3,8 +3,7 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Section header: brand title row (H1 + open-site button) + tagline, followed
7
- * by the purple ad banner that promotes the catalog stats.
6
+ * 板块头部:品牌标题行(H1 + 官网入口按钮)+ 副标题,其后是宣传目录统计的紫色广告条。
8
7
  */
9
8
  import { createElement as h, Fragment } from 'react'
10
9
  import styles from '../../styles/Header.module.css'
@@ -3,9 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Category tabs: an "all" chip followed by one chip per catalog category.
7
- * The "all" chip carries the total plugin count; the per-category chips
8
- * carry no counts so their widths stay uniform.
6
+ * 分类 Tab:先是「全部」chip,其后每个目录分类一个 chip。「全部」chip 带插件总数,
7
+ * 各分类 chip 不带计数以保持宽度一致。
9
8
  */
10
9
  import { createElement as h } from 'react'
11
10
  import styles from '../../styles/Header.module.css'