dsh-plugin 1.6.5 → 1.6.7
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/README.en.md +11 -11
- package/README.md +11 -11
- package/client/client.js +421 -390
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +132 -0
- package/lib/http/diagnostics.js +201 -0
- package/lib/http/install.js +399 -0
- package/lib/http/logs.js +164 -0
- package/lib/http/routes.js +19 -1263
- package/lib/http/settings.js +77 -0
- package/lib/http/shared.js +169 -0
- package/lib/http/task.js +208 -0
- package/lib/services/install/install.js +1 -1
- package/lib/services/install/package-entry.js +59 -8
- package/lib/services/install/preflight.js +47 -17
- package/lib/services/install/task-queue.js +75 -31
- package/lib/services/probe.js +1 -1
- package/lib/types/http/catalog.d.ts +10 -0
- package/lib/types/http/diagnostics.d.ts +4 -0
- package/lib/types/http/install.d.ts +11 -0
- package/lib/types/http/logs.d.ts +2 -0
- package/lib/types/http/routes.d.ts +15 -20
- package/lib/types/http/settings.d.ts +2 -0
- package/lib/types/http/shared.d.ts +44 -0
- package/lib/types/http/task.d.ts +3 -0
- package/lib/types/services/install/install.d.ts +1 -1
- package/lib/types/services/install/package-entry.d.ts +42 -10
- package/lib/types/services/install/preflight.d.ts +9 -1
- package/lib/types/services/install/task-queue.d.ts +6 -4
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +3 -0
- package/src/client/components/modals/NotificationsModal.tsx +34 -18
- package/src/client/components/modals/modals.tsx +38 -28
- package/src/client/components/views/InstalledView.tsx +23 -11
- package/src/client/hooks/useCatalog.ts +1 -1
- package/src/client/locales.ts +28 -12
- package/src/client/logic/failures.ts +36 -157
- package/src/client/logic/urls.ts +75 -4
- package/src/client/types.ts +1 -1
- package/src/server/http/catalog.ts +143 -0
- package/src/server/http/diagnostics.ts +193 -0
- package/src/server/http/install.ts +391 -0
- package/src/server/http/logs.ts +149 -0
- package/src/server/http/routes.ts +22 -1222
- package/src/server/http/settings.ts +73 -0
- package/src/server/http/shared.ts +191 -0
- package/src/server/http/task.ts +194 -0
- package/src/server/services/install/install-types.ts +9 -1
- package/src/server/services/install/install.ts +11 -1
- package/src/server/services/install/package-entry.ts +72 -9
- package/src/server/services/install/preflight.ts +47 -18
- package/src/server/services/install/task-queue.ts +73 -34
- package/src/server/services/probe.ts +1 -1
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* 安装前预检:对 GitHub 源的插件,检查其分发(codeload tarball
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* 安装前预检:对 GitHub 源的插件,检查其分发(codeload tarball)里是否真的包含
|
|
7
|
+
* package.json 声明的文件(main / exports["."] / dsh.bundle.patch)。git 分发常不提交
|
|
8
|
+
* 构建产物(lib/ 等),这类残缺包装完会让宿主重启加载插件树时 ERR_MODULE_NOT_FOUND
|
|
9
|
+
* 直接崩溃 —— 预检在动手安装前就把它们拦下,避免白装一次。
|
|
10
10
|
* npm 包信任 registry 的完整性,直接放行;任何无法预检的情况也放行,交给装后
|
|
11
11
|
* 校验(verifyInstalledEntry)兜底,保证预检自身失败不会卡死安装流程。
|
|
12
12
|
*/
|
|
@@ -19,7 +19,7 @@ import { pipeline } from 'node:stream/promises'
|
|
|
19
19
|
import { promisify } from 'node:util'
|
|
20
20
|
import { githubRefOf, githubRepoOf } from '../profile/profile.ts'
|
|
21
21
|
import { githubReleaseTarget } from './release-target.ts'
|
|
22
|
-
import {
|
|
22
|
+
import { requiredDistributionFiles, resolveBundlePatchFiles } from './package-entry.ts'
|
|
23
23
|
|
|
24
24
|
const execFileAsync = promisify(execFile)
|
|
25
25
|
|
|
@@ -27,7 +27,8 @@ const execFileAsync = promisify(execFile)
|
|
|
27
27
|
* 否则 `/install` 请求永远不返回、任务不入队、前端进度卡 0%。超时后放行(交给装后校验)。 */
|
|
28
28
|
const PREFLIGHT_TIMEOUT_MS = 15_000
|
|
29
29
|
|
|
30
|
-
/** 预检结果:ok=false
|
|
30
|
+
/** 预检结果:ok=false 表示分发改 package.json 声明的文件(入口或 bundle 补丁),
|
|
31
|
+
* missing 为该文件在包内的相对路径;
|
|
31
32
|
* name 为 git 分发包 package.json 声明的包名(非 git 目标/无法确定时为 null),
|
|
32
33
|
* 供安装路由做「包名冲突」检测 —— 同名包名已被其他来源占用时,pnpm 装前必然撞车,
|
|
33
34
|
* 需要把晦涩的 CLI 报错转成明确的拦截。 */
|
|
@@ -57,9 +58,17 @@ export async function preflightTarget(target: string): Promise<PreflightResult>
|
|
|
57
58
|
const rootPrefix = rootPackage.slice(0, -'package.json'.length)
|
|
58
59
|
const meta = await readTarJson(tar, rootPackage)
|
|
59
60
|
if (meta === null) return { ok: true, missing: null, name: null }
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// 只校验作者声明过的文件:没声明入口的包(只挂技能的 bundle)没有可缺失的入口,
|
|
62
|
+
// 它的分发是否完整由 dsh.bundle.patch 是否在包里决定;补丁若按本包名 mount 自己,
|
|
63
|
+
// 入口又变成必须的(宿主会 import 本包)—— 所以判入口前先把补丁读出来。
|
|
64
|
+
const patchTexts: string[] = []
|
|
65
|
+
for (const file of resolveBundlePatchFiles(meta)) {
|
|
66
|
+
const text = await readTarText(tar, `${rootPrefix}${file}`)
|
|
67
|
+
if (text !== null) patchTexts.push(text)
|
|
68
|
+
}
|
|
69
|
+
const required = requiredDistributionFiles(meta, patchTexts).map((file) => `${rootPrefix}${file}`)
|
|
70
|
+
const missing = missingDistributionFileOfTarList(await tarListing(tar), required)
|
|
71
|
+
return { ok: missing === null, missing, name: typeof meta.name === 'string' ? meta.name : null }
|
|
63
72
|
} catch {
|
|
64
73
|
return { ok: true, missing: null, name: null }
|
|
65
74
|
} finally {
|
|
@@ -125,31 +134,51 @@ export function rootPackageEntryOfTarList(listing: string): string | null {
|
|
|
125
134
|
return candidates.length === 1 ? candidates[0] : null
|
|
126
135
|
}
|
|
127
136
|
|
|
137
|
+
/**
|
|
138
|
+
* 清单里第一个缺失的必需文件(成员名带包根前缀,由调用方拼好),全部都在则为 null。
|
|
139
|
+
* 清单不可读(tar 缺失/解压失败)时按第一个必需文件缺失处理:与逐文件查询时
|
|
140
|
+
* 「查不到就是缺」的判定一致,宁可拦下一次可疑安装,也不放一个空包过去。
|
|
141
|
+
* Exported for a small parser regression test.
|
|
142
|
+
*/
|
|
143
|
+
export function missingDistributionFileOfTarList(listing: string | null, required: string[]): string | null {
|
|
144
|
+
if (required.length === 0) return null
|
|
145
|
+
if (listing === null) return required[0]
|
|
146
|
+
const present = new Set(listing.split(/\r?\n/))
|
|
147
|
+
return required.find((file) => !present.has(file)) ?? null
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
async function rootPackageEntry(tar: string): Promise<string | null> {
|
|
151
|
+
const listing = await tarListing(tar)
|
|
152
|
+
return listing === null ? null : rootPackageEntryOfTarList(listing)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** tar 成员清单;`tar` 执行失败时返回 null(调用方按无法判定处理)。 */
|
|
156
|
+
async function tarListing(tar: string): Promise<string | null> {
|
|
129
157
|
try {
|
|
130
158
|
const { stdout } = await execFileAsync('tar', ['-tzf', tar], { maxBuffer: 16 * 1024 * 1024 })
|
|
131
|
-
return
|
|
159
|
+
return stdout
|
|
132
160
|
} catch {
|
|
133
161
|
return null
|
|
134
162
|
}
|
|
135
163
|
}
|
|
136
164
|
|
|
137
|
-
/** 从 tarball
|
|
138
|
-
async function
|
|
165
|
+
/** 从 tarball 提取一个文件的内容;文件不存在或提取失败返回 null。 */
|
|
166
|
+
async function readTarText(tar: string, entry: string): Promise<string | null> {
|
|
139
167
|
try {
|
|
140
168
|
const { stdout } = await execFileAsync('tar', ['-xzOf', tar, entry], { maxBuffer: 4 * 1024 * 1024 })
|
|
141
|
-
return
|
|
169
|
+
return stdout
|
|
142
170
|
} catch {
|
|
143
171
|
return null
|
|
144
172
|
}
|
|
145
173
|
}
|
|
146
174
|
|
|
147
|
-
/**
|
|
148
|
-
async function
|
|
175
|
+
/** 从 tarball 提取并解析一个 JSON 文件;文件不存在或解析失败返回 null。 */
|
|
176
|
+
async function readTarJson(tar: string, entry: string): Promise<Record<string, unknown> | null> {
|
|
177
|
+
const text = await readTarText(tar, entry)
|
|
178
|
+
if (text === null) return null
|
|
149
179
|
try {
|
|
150
|
-
|
|
151
|
-
return stdout.split(/\r?\n/).some((line) => line === entry)
|
|
180
|
+
return JSON.parse(text) as Record<string, unknown>
|
|
152
181
|
} catch {
|
|
153
|
-
return
|
|
182
|
+
return null
|
|
154
183
|
}
|
|
155
184
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 输出流式收集到任务上;卸载成功时尝试从运行中 loader 即时停用(见 loader.ts)。
|
|
9
9
|
*/
|
|
10
10
|
import { spawn, spawnSync } from 'node:child_process'
|
|
11
|
-
import {
|
|
11
|
+
import { requiredDistributionFiles, resolveBundlePatchFiles, type PackageManifest } from './package-entry.ts'
|
|
12
12
|
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
13
13
|
import { dirname, join, resolve } from 'node:path'
|
|
14
14
|
import type { InstallResult, InstallTask, Invocation, QueueItem } from './install-types.ts'
|
|
@@ -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({
|
|
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({
|
|
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]
|
|
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
|
|
193
|
-
*
|
|
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 = {
|
|
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
|
-
//
|
|
420
|
-
//
|
|
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
|
-
//
|
|
540
|
-
//
|
|
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(' ')}`
|
|
@@ -596,10 +625,12 @@ function spawnMutation(options: {
|
|
|
596
625
|
}
|
|
597
626
|
|
|
598
627
|
/**
|
|
599
|
-
*
|
|
600
|
-
* git: 分发常不提交构建产物(lib/ 等),pnpm
|
|
601
|
-
* ERR_MODULE_NOT_FOUND 直接崩溃、网页打不开 ——
|
|
602
|
-
*
|
|
628
|
+
* 安装后校验已装包里 package.json 声明的文件(main / exports["."].default 与
|
|
629
|
+
* dsh.bundle.patch)是否真实存在。git: 分发常不提交构建产物(lib/ 等),pnpm 装完没有
|
|
630
|
+
* 报错,但宿主重启加载插件树时会 ERR_MODULE_NOT_FOUND 直接崩溃、网页打不开 —— 这里在
|
|
631
|
+
* 登记「待重启」前就把这类残缺包拦下。没有声明过的文件不在这里追究:没声明入口的
|
|
632
|
+
* bundle 包(只挂技能)本来就不需要入口,宿主读的是它的 package.json 与声明的补丁。
|
|
633
|
+
* 返回 { name, missing }:name 为空表示无法定位目标包(跳过校验);missing 为缺失文件的路径。
|
|
603
634
|
*/
|
|
604
635
|
export function verifyInstalledEntry(profile: string, target: string): { name: string | null; missing: string | null } {
|
|
605
636
|
let profilePkg: Record<string, unknown> | null = null
|
|
@@ -616,21 +647,29 @@ export function verifyInstalledEntry(profile: string, target: string): { name: s
|
|
|
616
647
|
: deps[target] !== undefined ? target : Object.keys(deps).find((k) => deps[k] === target)
|
|
617
648
|
if (!name) return { name: null, missing: null }
|
|
618
649
|
const pkgDir = join(profileDirectory(profile), 'node_modules', name)
|
|
619
|
-
let
|
|
650
|
+
let required: string[] = []
|
|
620
651
|
try {
|
|
621
|
-
const meta = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
652
|
+
const meta = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as PackageManifest
|
|
653
|
+
// 补丁按本包名 mount 自己时入口也是必须的,所以先读已装包里的补丁(读不到就当没有)。
|
|
654
|
+
const patchTexts = resolveBundlePatchFiles(meta).flatMap((file) => {
|
|
655
|
+
try {
|
|
656
|
+
return [readFileSync(join(pkgDir, file), 'utf8')]
|
|
657
|
+
} catch {
|
|
658
|
+
return []
|
|
659
|
+
}
|
|
660
|
+
})
|
|
661
|
+
required = requiredDistributionFiles(meta, patchTexts)
|
|
626
662
|
} catch {
|
|
627
663
|
return { name, missing: null }
|
|
628
664
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
665
|
+
for (const file of required) {
|
|
666
|
+
try {
|
|
667
|
+
if (!statSync(join(pkgDir, file)).isFile()) return { name, missing: file }
|
|
668
|
+
} catch {
|
|
669
|
+
return { name, missing: file }
|
|
670
|
+
}
|
|
633
671
|
}
|
|
672
|
+
return { name, missing: null }
|
|
634
673
|
}
|
|
635
674
|
|
|
636
675
|
/** 从 pnpm `Ignored build scripts:` 提示行解析被拦截的构建脚本 key(name@version,逗号分隔)。 */
|
|
@@ -645,7 +684,7 @@ function ignoredBuildKeys(output: string): string[] {
|
|
|
645
684
|
|
|
646
685
|
/** npm arborist edgesOut 崩溃 + 本机 npm 版本过低 → 把 [npm-too-low] 标记追加到任务输出,
|
|
647
686
|
* 前端据此展示「本机 npm 版本过低」的准确原因 —— 避免该报错被外层 ERR_PNPM_PREPARE_PACKAGE
|
|
648
|
-
*
|
|
687
|
+
* 误判成插件打包/分发问题。检测逻辑在独立的 npm-check.ts(本机 npm 环境职责)。 */
|
|
649
688
|
function annotateNpmTooLow(result: InstallResult, options: {
|
|
650
689
|
action: 'add' | 'remove' | 'update'
|
|
651
690
|
task?: InstallTask
|
|
@@ -720,21 +759,21 @@ export async function runPluginMutation(options: {
|
|
|
720
759
|
}
|
|
721
760
|
}
|
|
722
761
|
}
|
|
723
|
-
// pnpm
|
|
762
|
+
// pnpm 安装本身成功,但目标包声明分发的文件缺失(git 分发缺构建产物 / 缺声明的补丁)→ 判为失败:
|
|
724
763
|
// 撤销刚登记的待重启,避免用户重启时宿主加载残缺包直接崩溃。
|
|
725
764
|
// 全局 npm 安装不进 profile(profile deps 里没有这些包),跳过入口校验
|
|
726
765
|
if (options.action !== 'remove' && result.exitCode === 0 && !result.timedOut && (options.globalNpm ?? []).length === 0) {
|
|
727
766
|
const verified = verifyInstalledEntry(options.profile, options.target)
|
|
728
767
|
if (verified.name !== null && verified.missing !== null) {
|
|
729
768
|
clearPendingRestart(options.displayTarget ?? options.target)
|
|
730
|
-
const detail = `[packaging] ${verified.name}:
|
|
769
|
+
const detail = `[packaging] ${verified.name}: declared file missing: ${verified.missing} (git distribution lacks build output — install the npm version or report to the author)`
|
|
731
770
|
if (options.task && options.task.status !== 'cancelled') {
|
|
732
771
|
options.task.status = 'failed'
|
|
733
772
|
options.task.exitCode = null
|
|
734
773
|
options.task.progress = 0
|
|
735
774
|
pushLine(options.task, detail)
|
|
736
775
|
}
|
|
737
|
-
return { exitCode: 1, timedOut: false, error: `
|
|
776
|
+
return { exitCode: 1, timedOut: false, error: `declared file missing: ${verified.missing}`, stdout: result.stdout, stderr: `${result.stderr}\n${detail}` }
|
|
738
777
|
}
|
|
739
778
|
}
|
|
740
779
|
// npm arborist edgesOut 崩溃 + 本机 npm 版本过低 → 追加 [npm-too-low] 标记,前端据此给准确原因
|
|
@@ -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
|
|
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()
|