dsh-plugin 1.6.7 → 1.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/client.js +356 -323
- package/client/client.js.map +1 -1
- package/lib/http/diagnostics.js +22 -4
- package/lib/http/install.js +7 -8
- package/lib/services/install/operation-log.js +49 -0
- package/lib/services/install/task-queue.js +31 -0
- package/lib/types/services/install/operation-log.d.ts +11 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +2 -2
- package/src/client/components/catalog/PluginCard.tsx +3 -3
- package/src/client/components/modals/NotificationsModal.tsx +24 -0
- package/src/client/components/modals/modals.tsx +27 -2
- package/src/client/locales.ts +33 -7
- package/src/client/logic/failures.ts +11 -1
- package/src/client/logic/normalize.ts +3 -1
- package/src/client/logic/urls.ts +30 -3
- package/src/client/styles/Modal.module.css +16 -0
- package/src/client/types.ts +2 -1
- package/src/server/http/diagnostics.ts +22 -4
- package/src/server/http/install.ts +6 -8
- package/src/server/services/install/operation-log.ts +46 -0
- package/src/server/services/install/task-queue.ts +28 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* 失败回捞:DSH 有时只把一行摘要(`dsh: plugin command failed; diagnostics: <path>`)
|
|
7
|
+
* 回显到控制台,真正的 pnpm 报错写进该路径的 operation 日志。这里从输出里解析出该路径
|
|
8
|
+
* 并读取其尾部,补进失败信息 —— 否则前端拿不到任何错误特征,只能把失败归为「未知」。
|
|
9
|
+
* 只允许读 DSH 自己的 operation 日志(路径含 .plugin-manager 且以 pnpm.log 结尾),
|
|
10
|
+
* 防止插件把任意路径打印进日志造成越权读取;任何异常都静默降级(返回 null),
|
|
11
|
+
* 不改变原有失败处理。
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from 'node:fs'
|
|
14
|
+
import { CAPTURE_LIMIT_BYTES } from './install-types.ts'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 从 DSH 输出里解析 operation 日志路径(`diagnostics: <path>`)。
|
|
18
|
+
* 只认 DSH 的 operation 日志:路径含 `.plugin-manager` 且以 `pnpm.log` 结尾;
|
|
19
|
+
* 不是这种路径(或压根没有该行)返回 null,调用方据此跳过回捞。
|
|
20
|
+
*/
|
|
21
|
+
export function operationLogPathOf(output: string): string | null {
|
|
22
|
+
for (const raw of output.split(/\r?\n/)) {
|
|
23
|
+
const m = /diagnostics:\s*"?(.+?)"?\s*$/i.exec(raw.trim())
|
|
24
|
+
if (m === null) continue
|
|
25
|
+
const path = m[1].trim()
|
|
26
|
+
if (!path.includes('.plugin-manager') || !path.toLowerCase().endsWith('pnpm.log')) continue
|
|
27
|
+
return path
|
|
28
|
+
}
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 回捞 operation 日志尾部,作为失败详情返回;拿不到(无路径 / 文件不存在 / 读失败 /
|
|
34
|
+
* 内容为空)返回 null。尾部截到与子进程输出相同的上限,避免超大日志撑爆内存与消息。
|
|
35
|
+
*/
|
|
36
|
+
export function backfillOperationLog(output: string): string | null {
|
|
37
|
+
const path = operationLogPathOf(output)
|
|
38
|
+
if (path === null) return null
|
|
39
|
+
try {
|
|
40
|
+
const text = readFileSync(path, 'utf8').trim()
|
|
41
|
+
if (text === '') return null
|
|
42
|
+
return text.length > CAPTURE_LIMIT_BYTES ? text.slice(-CAPTURE_LIMIT_BYTES) : text
|
|
43
|
+
} catch {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -14,6 +14,7 @@ import { dirname, join, resolve } from 'node:path'
|
|
|
14
14
|
import type { InstallResult, InstallTask, Invocation, QueueItem } from './install-types.ts'
|
|
15
15
|
import { CAPTURE_LIMIT_BYTES, MAX_TASKS, MAX_TASK_LINES } from './install-types.ts'
|
|
16
16
|
import { npmTooLowMarker } from './npm-check.ts'
|
|
17
|
+
import { backfillOperationLog } from './operation-log.ts'
|
|
17
18
|
import { cleanLine, estimateProgress } from '../profile/progress.ts'
|
|
18
19
|
import type { LoaderHandle } from '../loader.ts'
|
|
19
20
|
import { hasClientUi, isDshPlugin, mountLoadedEntry, removeLoadedEntry } from '../loader.ts'
|
|
@@ -566,6 +567,9 @@ function spawnMutation(options: {
|
|
|
566
567
|
})
|
|
567
568
|
|
|
568
569
|
return (async () => {
|
|
570
|
+
// 超时复核用基线:本次任务开始前目标是否已在 profile 里。已安装的话,超时后核对无法区分
|
|
571
|
+
// 「旧版本还在」与「刚装上」,那种情形(更新超时)不冒认成功。
|
|
572
|
+
const wasInstalled = action !== 'remove' && !isGlobalNpm && verifyInstalledEntry(profile, target).name !== null
|
|
569
573
|
let result = await spawnOnce(args)
|
|
570
574
|
// npm 11 的 arborist peer-set bug(`Cannot read properties of null (reading 'children')`)
|
|
571
575
|
// 会让 `npm install -g` 在解析阶段直接崩溃。首次失败后自动带 `--legacy-peer-deps` 重试一次:
|
|
@@ -577,6 +581,16 @@ function spawnMutation(options: {
|
|
|
577
581
|
if (task) pushLine(task, '[npm] peer-set resolution crashed npm (known npm bug) — retrying with --legacy-peer-deps')
|
|
578
582
|
result = await spawnOnce(retryArgs)
|
|
579
583
|
}
|
|
584
|
+
// 超时被强杀 ≠ 没装上:命中的安装可能已经落盘(日志里 pnpm 已打印 Done,只是 dsh 进程迟迟不退出
|
|
585
|
+
// 而被我们超时掐掉)。判失败之前先复核 profile —— 目标已在依赖里、且声明分发的入口文件都在,
|
|
586
|
+
// 就按成功收尾,避免「其实装好了却报安装失败」并被自动归档成未知失败。
|
|
587
|
+
if (result.timedOut && !wasInstalled && action !== 'remove' && !isGlobalNpm && task?.status !== 'cancelled') {
|
|
588
|
+
const verified = verifyInstalledEntry(profile, target)
|
|
589
|
+
if (verified.name !== null && verified.missing === null) {
|
|
590
|
+
result = { ...result, timedOut: false, exitCode: 0, error: null }
|
|
591
|
+
if (task) pushLine(task, '[timeout] the install had already completed — treating it as a success')
|
|
592
|
+
}
|
|
593
|
+
}
|
|
580
594
|
// 统一终态后处理:exitCode/status/needsRestart 以最后一次尝试为准
|
|
581
595
|
if (task) {
|
|
582
596
|
task.exitCode = result.exitCode
|
|
@@ -776,6 +790,20 @@ export async function runPluginMutation(options: {
|
|
|
776
790
|
return { exitCode: 1, timedOut: false, error: `declared file missing: ${verified.missing}`, stdout: result.stdout, stderr: `${result.stderr}\n${detail}` }
|
|
777
791
|
}
|
|
778
792
|
}
|
|
793
|
+
// DSH 只把一行摘要(`dsh: plugin command failed; diagnostics: <path>`)回显到控制台时,
|
|
794
|
+
// 控制台里没有任何错误特征,前端只能把失败归为「未知」(issue #192)。这里按该路径回捞
|
|
795
|
+
// operation 日志尾部补进失败信息:用户直接看到真实报错,自动归类也有据可依。
|
|
796
|
+
// 只在本就不含任何错误码时回捞 —— pnpm 已把报错回显出来时无需多读一次文件。
|
|
797
|
+
if (result.exitCode !== 0 && !result.timedOut && !/\bERR_[A-Z0-9_]+\b/.test(`${result.stderr}\n${result.stdout}`)) {
|
|
798
|
+
const backfilled = backfillOperationLog(`${result.stderr}\n${result.stdout}`)
|
|
799
|
+
if (backfilled !== null) {
|
|
800
|
+
if (options.task && options.task.status !== 'cancelled') {
|
|
801
|
+
pushLine(options.task, '[pnpm log] DSH did not echo pnpm output — recovered the operation log from disk')
|
|
802
|
+
for (const line of backfilled.split(/\r?\n/)) if (line.trim() !== '') pushLine(options.task, line.trimEnd())
|
|
803
|
+
}
|
|
804
|
+
result = { ...result, stderr: `${result.stderr}\n${backfilled}` }
|
|
805
|
+
}
|
|
806
|
+
}
|
|
779
807
|
// npm arborist edgesOut 崩溃 + 本机 npm 版本过低 → 追加 [npm-too-low] 标记,前端据此给准确原因
|
|
780
808
|
return annotateNpmTooLow(result, options)
|
|
781
809
|
}
|