dsh-plugin 1.3.6 → 1.3.8
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 +43 -18
- package/README.md +43 -18
- package/client/client.js +486 -334
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +84 -5
- package/lib/services/probe.js +43 -0
- package/lib/types/services/probe.d.ts +11 -0
- package/package.json +3 -3
- package/src/client/components/PluginHubSection.tsx +35 -0
- package/src/client/components/catalog/CatalogList.tsx +11 -3
- package/src/client/components/modals/NotificationsModal.tsx +18 -2
- package/src/client/components/modals/modals.tsx +18 -3
- package/src/client/components/views/DiagnosticsView.tsx +14 -4
- package/src/client/components/views/MarketView.tsx +5 -1
- package/src/client/components/views/SettingsView.tsx +75 -14
- package/src/client/hooks/useTaskQueue.ts +6 -2
- package/src/client/locales.ts +31 -4
- package/src/client/logic/failures.ts +38 -9
- package/src/client/logic/renderMarkdown.ts +2 -2
- package/src/client/logic/urls.ts +24 -17
- package/src/client/styles/List.module.css +24 -1
- package/src/client/styles/Modal.module.css +48 -7
- package/src/client/styles/SettingsView.module.css +24 -0
- package/src/server/http/routes.ts +81 -6
- package/src/server/services/probe.ts +43 -0
|
@@ -13,7 +13,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
|
13
13
|
import { homedir, release } from 'node:os'
|
|
14
14
|
import { dirname, join, resolve } from 'node:path'
|
|
15
15
|
import { spawn } from 'node:child_process'
|
|
16
|
-
import { fetchViaCurl, probeUrl, systemProxy } from '../services/probe.ts'
|
|
16
|
+
import { fetchViaCurl, gitLsRemote, probeUrl, systemProxy } from '../services/probe.ts'
|
|
17
17
|
import { activeTask, cancelTask, dumpLoaderEntries, getTask, githubRepoOf, githubTarget, globalNpmPackagesOf, hasQueuedTarget, installTargetOf, listPendingRestarts, readProfileArg, startPluginMutation, validPackageName, type LoaderHandle } from '../services/install/install.ts'
|
|
18
18
|
import { recordInstalledVersion, recordResolvedNpmPackage, readInstalledVersions, removeInstalledVersion } from '../services/profile/installed-versions.ts'
|
|
19
19
|
import { resolveNpmPackage } from '../services/install/npm-resolve.ts'
|
|
@@ -494,11 +494,19 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
494
494
|
// 连通性自检直接打安装通道真实访问的轻量资源:npm 源拉包元数据、git 通道探
|
|
495
495
|
// 仓库主页、目录站探 badge 小文件,避免拉取目录全量 JSON。
|
|
496
496
|
// npm 行 display:配置了镜像就显示镜像地址;未配置置空 —— 客户端显示「未配置(跟随本机 npm 配置)」。
|
|
497
|
-
const allChecks: Array<{ key: string; url: string; display: string; cmd: string }> = [
|
|
497
|
+
const allChecks: Array<{ key: string; url: string; display: string; cmd: string; proxy?: string; git?: boolean }> = [
|
|
498
498
|
{ key: 'npm', url: `${registry}/dsh-plugin`, display: settings.npmRegistry !== '' ? registry : '', cmd: `npm view dsh-plugin version --registry ${registry}` },
|
|
499
|
-
|
|
499
|
+
// GitHub 行用真实 git 克隆握手(git ls-remote)打 dshplugin/hello-dsh 小仓库:
|
|
500
|
+
// 「网页能打开」和「git 能克隆」是两码事,握手成功才算通道通;小仓库秒级完成,不打 17MB 的 dsh-plugin-hub。
|
|
501
|
+
{ key: 'github', url: 'https://github.com/dshplugin/hello-dsh', display: 'github.com/dshplugin/hello-dsh', cmd: 'git ls-remote https://github.com/dshplugin/hello-dsh', git: true },
|
|
500
502
|
{ key: 'catalog', url: 'https://api.dsh-plugin.org/stats.json', display: 'api.dsh-plugin.org', cmd: 'curl -s https://api.dsh-plugin.org/stats.json' },
|
|
501
503
|
]
|
|
504
|
+
// 配置了 HTTP 代理:追加一行代理诊断 —— 用该代理打 github.com(安装通道真实访问的地址),
|
|
505
|
+
// 验证「代理能不能把请求带出去」。与安装同口径:curl 子进程注入该代理 env。
|
|
506
|
+
// 设置里未配置代理时不显示该行(跟随系统代理/直连,无「代理本身」可验证)。
|
|
507
|
+
if (settings.proxy !== '') {
|
|
508
|
+
allChecks.push({ key: 'proxy', url: 'https://github.com', display: settings.proxy, cmd: `curl -s -o /dev/null -x ${settings.proxy} https://github.com`, proxy: settings.proxy })
|
|
509
|
+
}
|
|
502
510
|
const checks = onlyKey !== '' ? allChecks.filter((c) => c.key === onlyKey) : allChecks
|
|
503
511
|
response.writeHead(200, {
|
|
504
512
|
'content-type': 'application/x-ndjson; charset=utf-8',
|
|
@@ -508,17 +516,22 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
508
516
|
// 探测走代理的优先级:设置里的代理 → 系统代理(macOS scutil / Windows 注册表)→ 环境变量 HTTPS_PROXY → 直连。
|
|
509
517
|
// 与安装通道一致(npm/git 靠 HTTP_PROXY 环境变量走代理),且自动跟随系统代理 ——
|
|
510
518
|
// 浏览器挂着代理能开、诊断就能测通,不用用户手动把代理填进设置。
|
|
519
|
+
// proxy 行例外:它验证的正是「设置里配的那个代理」,必须显式用它(而非 effectiveProxy)去探测。
|
|
511
520
|
const effectiveProxy = settings.proxy !== ''
|
|
512
521
|
? settings.proxy
|
|
513
522
|
: (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '')
|
|
514
|
-
const probe = (url: string) => probeUrl(url, effectiveProxy, 6000)
|
|
523
|
+
const probe = (url: string, proxy?: string) => probeUrl(url, proxy ?? effectiveProxy, 6000)
|
|
515
524
|
// 逐路串行:一条命令一条结果地推送给终端,保持「程序在跑」的真实感
|
|
516
525
|
const results: Array<{ key: string; ok: boolean }> = []
|
|
517
526
|
for (const c of checks) {
|
|
518
527
|
send({ type: 'probe', key: c.key, cmd: c.cmd, display: c.display })
|
|
519
|
-
|
|
528
|
+
// git 行走真实克隆握手(gitLsRemote),其余走 curl 探测,两路都带超时兜底
|
|
529
|
+
const r = c.git
|
|
530
|
+
? await gitLsRemote(c.url, c.proxy ?? effectiveProxy, 6000)
|
|
531
|
+
: await probe(c.url, c.proxy)
|
|
520
532
|
results.push({ key: c.key, ok: r.ok })
|
|
521
|
-
|
|
533
|
+
// git 行的 status 是退出码,客户端 git 行只显示 OK / 不可达,不显示成 HTTP 码
|
|
534
|
+
send({ type: r.ok ? 'ok' : 'fail', key: c.key, display: c.display, ms: r.ms, status: c.git ? null : r.status })
|
|
522
535
|
}
|
|
523
536
|
send({ type: 'end', at: Date.now() })
|
|
524
537
|
response.end()
|
|
@@ -535,6 +548,35 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
535
548
|
})
|
|
536
549
|
},
|
|
537
550
|
}),
|
|
551
|
+
webServer.register({
|
|
552
|
+
kind: 'exact',
|
|
553
|
+
path: '/dsh-plugin-hub/proxy-check',
|
|
554
|
+
handler: async (request, response) => {
|
|
555
|
+
if (!requireTrustedPost(request, response)) return
|
|
556
|
+
// 代理连通性校验:用待测代理打 dshplugin/hello-dsh 真实仓库页(权威可达目标),
|
|
557
|
+
// 供设置页输入代理时实时反馈「这个地址通不通」。不通过也允许保存 ——
|
|
558
|
+
// 用户可能是先填地址后开代理,因此这里只报告探测结果、不拦截保存。
|
|
559
|
+
let proxy = ''
|
|
560
|
+
let target = 'https://github.com/dshplugin/hello-dsh'
|
|
561
|
+
try {
|
|
562
|
+
const body = await readJsonBody(request)
|
|
563
|
+
if (body !== null && typeof body === 'object') {
|
|
564
|
+
const b = body as { proxy?: unknown; target?: unknown }
|
|
565
|
+
if (typeof b.proxy === 'string') proxy = b.proxy.trim()
|
|
566
|
+
if (typeof b.target === 'string' && b.target.trim() !== '') target = b.target.trim()
|
|
567
|
+
}
|
|
568
|
+
} catch {
|
|
569
|
+
// 空 body / 非 JSON:按默认目标探测
|
|
570
|
+
}
|
|
571
|
+
if (proxy === '') {
|
|
572
|
+
sendJson(response, 200, { ok: false, ms: null, status: null, target, reason: 'empty' })
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
// 探测走该代理本身(不是 effectiveProxy):验证的就是用户填的这个地址
|
|
576
|
+
const r = await probeUrl(target, proxy, 6000)
|
|
577
|
+
sendJson(response, 200, { ok: r.ok, ms: r.ms, status: r.status, target })
|
|
578
|
+
},
|
|
579
|
+
}),
|
|
538
580
|
webServer.register({
|
|
539
581
|
kind: 'exact',
|
|
540
582
|
path: '/dsh-plugin-hub/catalog',
|
|
@@ -605,6 +647,10 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
605
647
|
? (body as { source: string }).source
|
|
606
648
|
: ''
|
|
607
649
|
const settings = loadSettings(profile)
|
|
650
|
+
// 界面语言(客户端随请求携带):错误消息按用户当前语言提示(中文界面给中文,英文界面给英文)
|
|
651
|
+
const lang = typeof body === 'object' && body !== null && (body as { lang?: unknown }).lang === 'en'
|
|
652
|
+
? 'en'
|
|
653
|
+
: 'zh'
|
|
608
654
|
// 全局 npm 安装(官方 README 的 `npm install -g <pkgs>`,如 dsh-tui 的安装命令):
|
|
609
655
|
// 直接从命令框输入格式执行全局安装,不进任何 profile、无需宿主重启。
|
|
610
656
|
// 包列表优先取 body.globalNpm(客户端命令框解析后显式携带);兜底再从 rawRepo 解析,
|
|
@@ -703,6 +749,35 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
703
749
|
sendJson(response, 409, { error: `already queued: ${target}` })
|
|
704
750
|
return
|
|
705
751
|
}
|
|
752
|
+
// 安装前网络连通性预检:npm 通道探 registry、git 通道探 github.com 连通性。
|
|
753
|
+
// 探测目标固定为通道的稳定入口(registry 根 / github.com 主页),不探具体包或仓库
|
|
754
|
+
// 页 —— 404 表示「目标不存在」而非网络不通,不该被当成网络故障拦截。
|
|
755
|
+
// 仅新安装预检(更新是已信任目标的覆盖重装,跳过);不通直接 400 拦下并打
|
|
756
|
+
// [network] 标记,客户端据此提示「你的网络不通」,而不是把网络失败当成
|
|
757
|
+
// 插件侧问题引导去作者仓库提 Issue(issue #10:git fetch 超时被误报为插件问题)。
|
|
758
|
+
// 代理口径与诊断一致:设置里的代理 → 系统代理 → 环境变量 → 直连。
|
|
759
|
+
if (already === null) {
|
|
760
|
+
const effectiveProxy = settings.proxy !== ''
|
|
761
|
+
? settings.proxy
|
|
762
|
+
: (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '')
|
|
763
|
+
const isGitChannel = repoTarget !== null && target === repoTarget
|
|
764
|
+
const probeTarget = isGitChannel
|
|
765
|
+
? 'https://github.com/'
|
|
766
|
+
: `${(settings.npmRegistry.replace(/\/+$/, '') || 'https://registry.npmjs.org')}/`
|
|
767
|
+
const net = await probeUrl(probeTarget, effectiveProxy, 6000)
|
|
768
|
+
if (!net.ok) {
|
|
769
|
+
// 错误消息按客户端界面语言提示:中文界面给中文、英文界面给英文,
|
|
770
|
+
// 不再写死英文(用户一眼看懂是网络问题,而不是插件问题)
|
|
771
|
+
const channelName = isGitChannel
|
|
772
|
+
? (lang === 'zh' ? 'GitHub' : 'github.com')
|
|
773
|
+
: (lang === 'zh' ? 'npm 源' : 'the npm registry')
|
|
774
|
+
const error = lang === 'zh'
|
|
775
|
+
? `[network] 安装已中止:安装前无法连接到 ${channelName}(${probeTarget})—— 您的网络似乎不通或被拦截(断网 / DNS 解析失败 / 防火墙 / 代理配置问题)。这不是插件本身的问题,请检查网络连接或代理设置,运行「系统诊断」检测各通道连通性后重试。`
|
|
776
|
+
: `[network] install aborted: cannot reach ${channelName} (${probeTarget}) before install — your network connection appears to be down or blocked (DNS / proxy / firewall). This is not a problem with the plugin itself. Check your connection or proxy settings, run the connectivity diagnostic, then retry.`
|
|
777
|
+
sendJson(response, 400, { error, attempts })
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
}
|
|
706
781
|
// 安装前预检:仅新安装走预检(github 源分发改入口文件缺失时直接拦截)。
|
|
707
782
|
// 更新是已信任目标的覆盖重装,跳过预检避免重复下载 tarball。
|
|
708
783
|
// npm 包信任 registry 直接放行;github 源拦截时标记 [packaging] 前缀,
|
|
@@ -124,6 +124,49 @@ export function probeUrl(url: string, proxy: string, timeoutMs: number): Promise
|
|
|
124
124
|
return curlProbe(target.href, proxy, timeoutMs)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* git 通道真实克隆握手探测:spawn git ls-remote(https 传输,与 pnpm 克隆前的
|
|
129
|
+
* ref 握手一致),注入代理 env,GIT_TERMINAL_PROMPT=0 防凭据提示挂起。
|
|
130
|
+
*
|
|
131
|
+
* 为什么要真实 git 而非 curl 打网页:网页「能打开」和 git「能克隆」是两码事 ——
|
|
132
|
+
* 防火墙/代理常按端口与协议区分,HTTP 页可达不代表 git 传输可达。这里测的就是
|
|
133
|
+
* 克隆握手本身,回答「github:owner/repo 装不装得动」。
|
|
134
|
+
* 供系统诊断 GitHub 通道使用;探测目标用 dshplugin/hello-dsh 小仓库(秒级完成,
|
|
135
|
+
* 不打 17MB 的 dsh-plugin-hub 主页)。
|
|
136
|
+
*/
|
|
137
|
+
export function gitLsRemote(url: string, proxy: string, timeoutMs: number): Promise<ProbeResult> {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
const started = Date.now()
|
|
140
|
+
const env: NodeJS.ProcessEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' }
|
|
141
|
+
if (proxy !== '') {
|
|
142
|
+
env.HTTP_PROXY = proxy
|
|
143
|
+
env.HTTPS_PROXY = proxy
|
|
144
|
+
env.http_proxy = proxy
|
|
145
|
+
env.https_proxy = proxy
|
|
146
|
+
}
|
|
147
|
+
const child = spawn('git', ['ls-remote', '--exit-code', url, 'HEAD'], { env })
|
|
148
|
+
let out = ''
|
|
149
|
+
child.stdout.on('data', (c: Buffer) => { out += c.toString() })
|
|
150
|
+
// git 的报错不透传给终端:结果以退出码 + HEAD ref 是否返回为准
|
|
151
|
+
child.stderr.on('data', () => {})
|
|
152
|
+
let done = false
|
|
153
|
+
const finish = (r: ProbeResult) => {
|
|
154
|
+
if (done) return
|
|
155
|
+
done = true
|
|
156
|
+
clearTimeout(timer)
|
|
157
|
+
resolve(r)
|
|
158
|
+
}
|
|
159
|
+
child.on('error', () => finish(probeFail()))
|
|
160
|
+
child.on('close', (code) => {
|
|
161
|
+
const ok = code === 0 && out.trim() !== ''
|
|
162
|
+
// status 复用为 git 退出码(0 = 克隆握手成功);客户端 git 行不把它显示成 HTTP 码
|
|
163
|
+
finish({ ok, ms: Date.now() - started, status: code })
|
|
164
|
+
})
|
|
165
|
+
// 超时兜底:与 curlProbe 同口径,防止 git 挂起
|
|
166
|
+
const timer = setTimeout(() => { child.kill(); finish(probeFail()) }, timeoutMs + 1000)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
127
170
|
/**
|
|
128
171
|
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入)。
|
|
129
172
|
* 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,
|