dsh-plugin 1.6.4 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.en.md +11 -11
  2. package/README.md +11 -11
  3. package/client/client.js +450 -405
  4. package/client/client.js.map +1 -1
  5. package/lib/http/catalog.js +132 -0
  6. package/lib/http/diagnostics.js +201 -0
  7. package/lib/http/install.js +399 -0
  8. package/lib/http/logs.js +164 -0
  9. package/lib/http/routes.js +19 -1261
  10. package/lib/http/settings.js +77 -0
  11. package/lib/http/shared.js +169 -0
  12. package/lib/http/task.js +208 -0
  13. package/lib/services/install/install.js +1 -1
  14. package/lib/services/install/npm-resolve.js +44 -15
  15. package/lib/services/install/task-queue.js +45 -16
  16. package/lib/services/probe.js +18 -1
  17. package/lib/types/http/catalog.d.ts +10 -0
  18. package/lib/types/http/diagnostics.d.ts +4 -0
  19. package/lib/types/http/install.d.ts +11 -0
  20. package/lib/types/http/logs.d.ts +2 -0
  21. package/lib/types/http/routes.d.ts +15 -20
  22. package/lib/types/http/settings.d.ts +2 -0
  23. package/lib/types/http/shared.d.ts +44 -0
  24. package/lib/types/http/task.d.ts +3 -0
  25. package/lib/types/services/install/install.d.ts +1 -1
  26. package/lib/types/services/install/npm-resolve.d.ts +3 -2
  27. package/lib/types/services/probe.d.ts +8 -0
  28. package/package.json +1 -1
  29. package/src/client/components/PluginHubSection.tsx +7 -2
  30. package/src/client/components/modals/HubUpdateModal.tsx +19 -10
  31. package/src/client/components/modals/NotificationsModal.tsx +34 -18
  32. package/src/client/components/modals/modals.tsx +38 -28
  33. package/src/client/components/views/InstalledView.tsx +23 -11
  34. package/src/client/hooks/useCatalog.ts +13 -9
  35. package/src/client/locales.ts +28 -8
  36. package/src/client/logic/failures.ts +36 -157
  37. package/src/client/logic/urls.ts +72 -1
  38. package/src/client/types.ts +11 -0
  39. package/src/server/http/catalog.ts +143 -0
  40. package/src/server/http/diagnostics.ts +193 -0
  41. package/src/server/http/install.ts +391 -0
  42. package/src/server/http/logs.ts +149 -0
  43. package/src/server/http/routes.ts +22 -1220
  44. package/src/server/http/settings.ts +73 -0
  45. package/src/server/http/shared.ts +191 -0
  46. package/src/server/http/task.ts +194 -0
  47. package/src/server/services/install/install-types.ts +9 -1
  48. package/src/server/services/install/install.ts +11 -1
  49. package/src/server/services/install/npm-resolve.ts +41 -13
  50. package/src/server/services/install/task-queue.ts +45 -16
  51. package/src/server/services/probe.ts +19 -1
@@ -58,12 +58,30 @@ export function activeTask(): ActiveTaskInfo[] {
58
58
  for (const item of queue) {
59
59
  const task = item.task
60
60
  if (task.status === 'pending') {
61
- found.push({ id: task.id, target: show(task), action: task.action, status: 'pending', progress: task.progress, lines: task.lines.slice(0, MAX_TASK_LINES), attempts: task.attempts, installChannel: task.installChannel })
61
+ found.push({
62
+ id: task.id,
63
+ target: show(task),
64
+ action: task.action,
65
+ status: 'pending',
66
+ progress: task.progress,
67
+ lines: task.lines.slice(0, MAX_TASK_LINES),
68
+ attempts: task.attempts,
69
+ installChannel: task.installChannel,
70
+ })
62
71
  }
63
72
  }
64
73
  tasks.forEach((task) => {
65
74
  if (task.status === 'running') {
66
- found.unshift({ id: task.id, target: show(task), action: task.action, status: 'running', progress: task.progress, lines: task.lines.slice(0, MAX_TASK_LINES), attempts: task.attempts, installChannel: task.installChannel })
75
+ found.unshift({
76
+ id: task.id,
77
+ target: show(task),
78
+ action: task.action,
79
+ status: 'running',
80
+ progress: task.progress,
81
+ lines: task.lines.slice(0, MAX_TASK_LINES),
82
+ attempts: task.attempts,
83
+ installChannel: task.installChannel,
84
+ })
67
85
  }
68
86
  })
69
87
  return found
@@ -134,7 +152,7 @@ function cliInvocation(profile: string): Invocation {
134
152
  if (entry !== undefined) {
135
153
  // 打包桌面端(Electron):宿主入口在 app.asar 内,机器上根本没有 `dsh` 可执行文件
136
154
  //(Windows 那条 dsh.cmd shim 只存在于宿主进程自己的 PATH 里),直接回退 PATH 必然
137
- // 报 [dsh-missing](见 issue #71)。这里改用应用自己的 CLI 引导脚本调起同一份打包
155
+ // 报 [dsh-missing]。这里改用应用自己的 CLI 引导脚本调起同一份打包
138
156
  // CLI:Electron 可执行文件 + --expose-internals + desktop-cli.js,与内置终端、
139
157
  // 官方插件页安装完全同一条路径 —— desktop profile 由应用独占管理的授权也在其中。
140
158
  const bootstrap = desktopCliBootstrap(entry)
@@ -189,9 +207,8 @@ export function desktopCliBootstrap(entry: string): string | null {
189
207
 
190
208
  /**
191
209
  * dsh 命令存在性探测:which/where 找 PATH 里的 dsh。
192
- * Windows shell 模式命令缺失时 cmd 只输出 GBK 乱码「不是内部或外部命令」(UTF-8 解码后
193
- * 无法匹配),POSIX 非 shell 模式才会抛 spawn dsh ENOENT —— 两种形态都不可靠,统一在
194
- * spawn 前用 which/where 探测,命令不存在则直接失败并打 ASCII 标记 [dsh-missing](乱码免疫)。
210
+ * 命令缺失时各平台报错形态不同(Windows shell 只回 GBK 乱码、POSIX 才抛 spawn ENOENT),
211
+ * 统一在 spawn 前用 which/where 探测,命令不存在则直接失败并打 ASCII 标记 [dsh-missing](乱码免疫)。
195
212
  * 探测自身异常(which/where 不可用等)不拦截安装,交给装后校验兜底。
196
213
  */
197
214
  function dshOnPath(env?: NodeJS.ProcessEnv): boolean {
@@ -270,7 +287,22 @@ export function startPluginMutation(options: {
270
287
  // 卸载入队时先快照「该包是否带客户端 UI」:卸载完成后包已从磁盘删除,读不到 package.json,
271
288
  // 只能在此刻取;卸载成功且曾存活时据此决定是否要刷新页面摘除插件面板
272
289
  const hadClientUi = options.action === 'remove' ? hasClientUi(options.profile, options.target) : undefined
273
- const task: InstallTask = { id: nextTaskId, target: options.target, displayTarget: options.displayTarget, globalNpm: options.globalNpm, action: options.action, status: 'pending', timedOut: false, exitCode: null, progress: 0, lines: [], attempts: options.attempts ?? [], needsRestart: false, hadClientUi, installChannel: options.installChannel }
290
+ const task: InstallTask = {
291
+ id: nextTaskId,
292
+ target: options.target,
293
+ displayTarget: options.displayTarget,
294
+ globalNpm: options.globalNpm,
295
+ action: options.action,
296
+ status: 'pending',
297
+ timedOut: false,
298
+ exitCode: null,
299
+ progress: 0,
300
+ lines: [],
301
+ attempts: options.attempts ?? [],
302
+ needsRestart: false,
303
+ hadClientUi,
304
+ installChannel: options.installChannel,
305
+ }
274
306
  nextTaskId = nextTaskId >= Number.MAX_SAFE_INTEGER ? 1 : nextTaskId + 1
275
307
  tasks.set(task.id, task)
276
308
  if (tasks.size > MAX_TASKS) {
@@ -416,8 +448,8 @@ function spawnMutation(options: {
416
448
  let child: ReturnType<typeof spawn>
417
449
  try {
418
450
  // 独立 CLI 场景(file: 'dsh',宿主加载器走 node bin 不经过这里):先探测 dsh 是否在 PATH。
419
- // 命令不存在时 Windows shell 模式只回传 GBK 乱码、POSIX 才抛 spawn dsh ENOENT —— 统一前置
420
- // 拦截并打 ASCII 标记 [dsh-missing],前端据此精准提示「dsh 未加入 PATH」而不是笼统的插件失败。
451
+ // 命令缺失时各平台报错形态不同(GBK 乱码 / spawn ENOENT),统一前置拦截并打 ASCII 标记
452
+ // [dsh-missing],前端据此精准提示「dsh 未加入 PATH」而不是笼统的插件失败。
421
453
  if (invocation.file === 'dsh' && !dshOnPath(env)) {
422
454
  const detail = '[dsh-missing] the dsh CLI was not found on this machine (DeepSeek Harness not installed, or dsh not on the PATH)'
423
455
  if (task) {
@@ -535,12 +567,9 @@ function spawnMutation(options: {
535
567
 
536
568
  return (async () => {
537
569
  let result = await spawnOnce(args)
538
- // npm 11 的 arborist peer-set bug(`Cannot read properties of null (reading 'children')`):
539
- // 某些包的 peerDependencies 用「双段 prerelease 范围」(如 dsh-tui@0.9.0 的 `^0.1.0-rc.6 || ^0.1.1-rc.1`,
540
- // 26 个必需 peer 全是这种形状)会让 `npm install -g` 在解析阶段直接崩溃 —— 退出码 1、什么都没装,
541
- // 与是不是从我们界面发起无关(终端里同样复现)。首次失败后自动带 `--legacy-peer-deps` 重试一次:
542
- // 跳过 peer 自动解析即可绕过该 bug(peer 通常由同装的 @deepseek-ai/dsh 依赖树天然提供)。
543
- // 二次尝试仍失败则保留其错误信息,不掩盖真正的原因。
570
+ // npm 11 的 arborist peer-set bug(`Cannot read properties of null (reading 'children')`)
571
+ // 会让 `npm install -g` 在解析阶段直接崩溃。首次失败后自动带 `--legacy-peer-deps` 重试一次:
572
+ // 跳过 peer 自动解析即可绕过该 bug;二次尝试仍失败则保留其错误信息,不掩盖真正的原因。
544
573
  if (isGlobalNpm && result.exitCode !== 0 && !result.timedOut && task?.status !== 'cancelled') {
545
574
  const retryArgs = ['install', '-g', '--legacy-peer-deps', ...(globalNpm ?? [])]
546
575
  const retryExecuted = `npm install -g --legacy-peer-deps ${(globalNpm ?? []).join(' ')}`
@@ -645,7 +674,7 @@ function ignoredBuildKeys(output: string): string[] {
645
674
 
646
675
  /** npm arborist edgesOut 崩溃 + 本机 npm 版本过低 → 把 [npm-too-low] 标记追加到任务输出,
647
676
  * 前端据此展示「本机 npm 版本过低」的准确原因 —— 避免该报错被外层 ERR_PNPM_PREPARE_PACKAGE
648
- * 误判成插件打包/分发问题、误导用户提 Issue。检测逻辑在独立的 npm-check.ts(本机 npm 环境职责)。 */
677
+ * 误判成插件打包/分发问题。检测逻辑在独立的 npm-check.ts(本机 npm 环境职责)。 */
649
678
  function annotateNpmTooLow(result: InstallResult, options: {
650
679
  action: 'add' | 'remove' | 'update'
651
680
  task?: InstallTask
@@ -165,7 +165,7 @@ function runCurl(args: string[], env: NodeJS.ProcessEnv, timeoutMs: number): Pro
165
165
  * 若本机 curl 不认这个参数(curl < 7.70)则去掉参数重跑一次,保证老版本不因此直接失败;
166
166
  * 降级后仍被吊销检查拦死(CRYPT_E_NO_REVOCATION_CHECK)时,再用 --ssl-no-revoke
167
167
  * 彻底关掉吊销检查跑一次 —— 该参数 curl ≥ 7.19 即存在,覆盖面比降级参数更广,
168
- * 否则「能上网但取不到 CRL」的 Windows 机器会把所有通道误报成不可达(见 issue #71)。
168
+ * 否则「能上网但取不到 CRL」的 Windows 机器会把所有通道误报成不可达。
169
169
  * 吊销检查只影响本次探测自身的信任判断,不改变系统 TLS 配置。 */
170
170
  async function curlWithTlsFallback(args: string[], env: NodeJS.ProcessEnv, timeoutMs: number): Promise<CurlRun> {
171
171
  const tlsArgs = curlTlsArgs()
@@ -256,6 +256,24 @@ export function gitLsRemote(url: string, proxy: string, timeoutMs: number): Prom
256
256
  })
257
257
  }
258
258
 
259
+ /**
260
+ * 请求一个 URL 并返回重定向后的最终地址(curl -I -L -w %{url_effective})。
261
+ * 供 GitHub 仓库改名解析使用:对已改名仓库 GitHub 回 301,跟随跳转后的最终地址
262
+ * 即规范地址。与其它 GitHub 访问同口径 —— curl 子进程注入代理 env(Node 内置
263
+ * http(s) 读不到系统代理,直连在部分网络下不可达)。
264
+ * 未发生跳转(200/404)、网络失败或 curl 不可用都返回 null,调用方保留原始地址。
265
+ */
266
+ export async function finalUrl(url: string, proxy: string, timeoutMs: number): Promise<string | null> {
267
+ const discard = process.platform === 'win32' ? 'NUL' : '/dev/null'
268
+ const r = await curlWithTlsFallback([
269
+ '-sS', '-I', '-L', '-o', discard, '-w', '%{url_effective}',
270
+ '--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))),
271
+ url,
272
+ ], proxyEnv(proxy), timeoutMs)
273
+ const final = r.stdout.trim()
274
+ return r.code === 0 && final !== '' ? final : null
275
+ }
276
+
259
277
  /**
260
278
  * curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入与 TLS 参数兜底)。
261
279
  * 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,