dsh-plugin 1.4.9 → 1.4.10
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 +353 -363
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +24 -15
- package/lib/services/install/npm-check.js +4 -5
- package/lib/services/install/task-queue.js +1 -1
- package/lib/services/probe.js +2 -2
- package/lib/services/profile/profile.js +1 -1
- package/lib/types/http/routes.d.ts +3 -0
- package/lib/types/services/install/npm-check.d.ts +1 -1
- package/lib/types/services/probe.d.ts +2 -2
- package/lib/types/services/profile/profile.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +1 -1
- package/src/client/components/catalog/PluginCard.tsx +2 -2
- package/src/client/components/modals/NotificationsModal.tsx +13 -9
- package/src/client/components/modals/modals.tsx +10 -6
- package/src/client/data/catalog.ts +1 -1
- package/src/client/hooks/useCatalog.ts +2 -2
- package/src/client/locales.ts +7 -0
- package/src/client/logic/constants.ts +1 -1
- package/src/client/logic/failures.ts +76 -95
- package/src/client/logic/urls.ts +5 -2
- package/src/client/styles/InstalledView.module.css +1 -1
- package/src/client/styles/Modal.module.css +2 -2
- package/src/client/types.ts +1 -1
- package/src/server/http/routes.ts +22 -15
- package/src/server/services/install/npm-check.ts +4 -5
- package/src/server/services/install/task-queue.ts +1 -1
- package/src/server/services/probe.ts +2 -2
- package/src/server/services/profile/profile.ts +1 -1
package/lib/http/routes.js
CHANGED
|
@@ -22,7 +22,7 @@ import { loadSettings, saveSettings, resetSettings } from '../services/settings.
|
|
|
22
22
|
import { appendLog, clearLog, readLog, logFilePath, defaultLogFilePath, customLogFile } from '../services/log.js';
|
|
23
23
|
/**
|
|
24
24
|
* 跨平台宿主重启脚本(以 `node -e` 运行,独立于宿主进程)。
|
|
25
|
-
*
|
|
25
|
+
* 不用 `/bin/sh -c`:Windows 无 /bin/sh,且 lsof/nohup 仅 POSIX 存在。
|
|
26
26
|
* 由 `spawn(process.execPath, ['-e', SCRIPT, port], { detached, stdio:'ignore' })` 孵化,
|
|
27
27
|
* 宿主进程被 kill 后仍能完成「停旧 → 等端口释放 → 拉起新 dsh web」。
|
|
28
28
|
*/
|
|
@@ -137,8 +137,7 @@ function installedRepoOf(profile, name) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
/** 宿主 dsh CLI 版本:优先直接跑 `dsh --version`(与 pnpm/npm/git 同款同步探测,1500ms 超时兜底,
|
|
140
|
-
*
|
|
141
|
-
* 导致 issue 环境快照 DSH: unknown;拿不到再退回入口 package.json 查找(dsh 不在 PATH 时兜底)。 */
|
|
140
|
+
* 绝不挂起);拿不到再退回从入口 `process.argv[1]` 向上找 package.json(dsh 不在 PATH 时兜底)。 */
|
|
142
141
|
function hostDshVersion() {
|
|
143
142
|
const viaCli = toolVersion('dsh', ['--version']);
|
|
144
143
|
if (viaCli)
|
|
@@ -196,7 +195,7 @@ function hostEnv(profile) {
|
|
|
196
195
|
};
|
|
197
196
|
}
|
|
198
197
|
/** 安装/卸载 CLI 的子进程环境:透传宿主环境,并按设置注入 HTTP(S) 代理。
|
|
199
|
-
* npm registry
|
|
198
|
+
* 不注入 npm registry —— 安装完全沿用用户本机 npm 配置(~/.npmrc / 全局配置)。
|
|
200
199
|
* 代理优先级:设置里的代理 → 系统代理(macOS scutil / Windows 注册表)→ 宿主 env 原有值。
|
|
201
200
|
* 使安装通道与诊断使用相同的代理来源。 */
|
|
202
201
|
function mutationEnv(settings) {
|
|
@@ -229,14 +228,24 @@ function sendJson(response, status, value) {
|
|
|
229
228
|
});
|
|
230
229
|
response.end(JSON.stringify(value));
|
|
231
230
|
}
|
|
232
|
-
/** POST mutations are only accepted from the local web server origin.
|
|
233
|
-
|
|
231
|
+
/** POST mutations are only accepted from the local web server origin or the desktop host page.
|
|
232
|
+
* Exported for tests (tests/routes.test.ts) — the origin check is the whole CSRF defence. */
|
|
233
|
+
export function isSameOrigin(request) {
|
|
234
234
|
const origin = request.headers.origin;
|
|
235
|
-
|
|
236
|
-
if (origin === undefined || host === undefined)
|
|
235
|
+
if (origin === undefined)
|
|
237
236
|
return false;
|
|
238
237
|
try {
|
|
239
238
|
const url = new URL(origin);
|
|
239
|
+
// 桌面端(Electron 宿主)的页面跑在宿主自定义协议下,origin 形如 `dsh-app://app`。
|
|
240
|
+
// 客户端一律用相对路径请求,宿主把 `/dsh-plugin-hub/*` 转发给本地 http server 时
|
|
241
|
+
// Origin 头原样保留,与 Host(如 localhost:3081)不等,因此必须显式放行该协议,
|
|
242
|
+
// 否则桌面端全部 POST 都会被判成 untrusted origin。该协议只有宿主自身能产生,
|
|
243
|
+
// 外部网页无法把 Origin 伪造成 `dsh-app://…`,放行不扩大 CSRF 面。
|
|
244
|
+
if (url.protocol === 'dsh-app:')
|
|
245
|
+
return true;
|
|
246
|
+
const host = request.headers.host;
|
|
247
|
+
if (host === undefined)
|
|
248
|
+
return false;
|
|
240
249
|
const localHostnames = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
241
250
|
return url.host === host && localHostnames.has(url.hostname);
|
|
242
251
|
}
|
|
@@ -638,7 +647,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
638
647
|
handler: async (request, response) => {
|
|
639
648
|
if (!requireMethod(request, response, 'GET'))
|
|
640
649
|
return;
|
|
641
|
-
//
|
|
650
|
+
// 目录/统计数据服务端代理:浏览器不直连 dsh-plugin.org,统一经此路由
|
|
642
651
|
// 转发(curl 子进程注入代理 env),与 npm / git 安装通道走同一代理口径,
|
|
643
652
|
// 「npm / git / 目录数据请求统一走该代理」的设置文案因此真实生效。
|
|
644
653
|
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
@@ -696,7 +705,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
696
705
|
handler: async (request, response) => {
|
|
697
706
|
if (!requireTrustedPost(request, response))
|
|
698
707
|
return;
|
|
699
|
-
// 任务自动入队:即使已有插件操作在跑也接受请求(FIFO
|
|
708
|
+
// 任务自动入队:即使已有插件操作在跑也接受请求(FIFO 串行执行),不做 409 拒绝
|
|
700
709
|
try {
|
|
701
710
|
const body = await readJsonBody(request);
|
|
702
711
|
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm 对已存在依赖原位覆盖重装)。
|
|
@@ -835,7 +844,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
835
844
|
// 页 —— 404 表示「目标不存在」而非网络不通,不该被当成网络故障拦截。
|
|
836
845
|
// 仅新安装预检(更新是已信任目标的覆盖重装,跳过);不通直接 400 拦下并打
|
|
837
846
|
// [network] 标记,客户端据此提示「你的网络不通」,而不是把网络失败当成
|
|
838
|
-
// 插件侧问题引导去作者仓库提 Issue
|
|
847
|
+
// 插件侧问题引导去作者仓库提 Issue。
|
|
839
848
|
// 代理口径与诊断一致:设置里的代理 → 系统代理 → 环境变量 → 直连。
|
|
840
849
|
if (already === null) {
|
|
841
850
|
const effectiveProxy = settings.proxy !== ''
|
|
@@ -848,7 +857,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
848
857
|
const net = await probeUrl(probeTarget, effectiveProxy, 6000);
|
|
849
858
|
if (!net.ok) {
|
|
850
859
|
// 错误消息按客户端界面语言提示:中文界面给中文、英文界面给英文,
|
|
851
|
-
//
|
|
860
|
+
// 让用户一眼看懂是网络问题而非插件问题
|
|
852
861
|
const channelName = isGitChannel
|
|
853
862
|
? (lang === 'zh' ? 'GitHub' : 'github.com')
|
|
854
863
|
: (lang === 'zh' ? 'npm 源' : 'the npm registry');
|
|
@@ -875,9 +884,9 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
875
884
|
return;
|
|
876
885
|
}
|
|
877
886
|
// 包名冲突检测(仅 git 通道):git 目标包内声明的 name 若与 profile 已装依赖同名,
|
|
878
|
-
// pnpm 会以该 name 做依赖键撞车,抛出的 CLI
|
|
879
|
-
//
|
|
880
|
-
//
|
|
887
|
+
// pnpm 会以该 name 做依赖键撞车,抛出的 CLI 报错晦涩难懂 —— 且该包名在 registry 上
|
|
888
|
+
// 可能被别的仓库占用,npm 反查搜不到,git 直装就会撞上已装的同名依赖。
|
|
889
|
+
// 到这里已装依赖要么来源不同仓库、要么 spec 是版本号解析不出仓库身份。
|
|
881
890
|
// 读 node_modules 里已装包的真实 repository 区分两种情况:
|
|
882
891
|
// - 指向同一仓库 → 是「同仓库残留」(spec 丢了仓库身份),就地转 update 覆盖重装(幂等);
|
|
883
892
|
// - 指向别的仓库 / 读不到 → 跨仓库包名冲突,入队前转成明确 409,指导用户先卸载或改装。
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 本机 npm 环境检测:判断一次安装失败是否源于「本机 npm 版本过低」。
|
|
7
7
|
*
|
|
8
|
-
* 背景:pnpm 在 git 源插件的 prepare 阶段会调 npm 装依赖,npm arborist 在解 peer
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 该报错会被 pnpm 包进 ERR_PNPM_PREPARE_PACKAGE 输出,若按插件打包问题引导提 Issue 就误导了用户。
|
|
8
|
+
* 背景:pnpm 在 git 源插件的 prepare 阶段会调 npm 装依赖,npm arborist 在解 peer 依赖时有
|
|
9
|
+
* 已知内部崩溃 —— `Cannot read properties of null (reading 'edgesOut')`。这是 npm 自身缺陷,
|
|
10
|
+
* 该报错会被 pnpm 包进 ERR_PNPM_PREPARE_PACKAGE 输出,不能按插件打包问题引导用户提 Issue。
|
|
12
11
|
*
|
|
13
12
|
* 这里的 `npmTooLowMarker` 是纯函数(输出文本 + 环境 → 标记行或 null),不触碰任务/队列,
|
|
14
13
|
* 由调用方(task-queue.ts)负责把标记追加到任务输出;spawn 读 npm 版本是唯一副作用,独立可测。
|
|
@@ -16,7 +15,7 @@
|
|
|
16
15
|
import { spawnSync } from 'node:child_process';
|
|
17
16
|
/** npm arborist 内部崩溃特征:`Cannot read properties of null (reading 'edgesOut')`。 */
|
|
18
17
|
export const NPM_CRASH_EDGES_OUT_RE = /Cannot read properties of null \(reading 'edgesOut'\)|edgesOut/i;
|
|
19
|
-
/**
|
|
18
|
+
/** 低于该版本(该缺陷在 npm 11.6.0 修复)遇到 edgesOut 崩溃即判「本机 npm 版本过低」。 */
|
|
20
19
|
export const NPM_MIN_VERSION = [11, 6, 0];
|
|
21
20
|
/** 读本机 npm 版本(major.minor.patch);npm 不在 PATH 或执行失败/超时时返回 null(不妄下结论)。 */
|
|
22
21
|
export function npmVersionOf(env) {
|
|
@@ -444,7 +444,7 @@ function spawnMutation(options) {
|
|
|
444
444
|
});
|
|
445
445
|
return (async () => {
|
|
446
446
|
let result = await spawnOnce(args);
|
|
447
|
-
// npm 11 的 arborist peer-set bug(`Cannot read properties of null (reading 'children')
|
|
447
|
+
// npm 11 的 arborist peer-set bug(`Cannot read properties of null (reading 'children')`):
|
|
448
448
|
// 某些包的 peerDependencies 用「双段 prerelease 范围」(如 dsh-tui@0.9.0 的 `^0.1.0-rc.6 || ^0.1.1-rc.1`,
|
|
449
449
|
// 26 个必需 peer 全是这种形状)会让 `npm install -g` 在解析阶段直接崩溃 —— 退出码 1、什么都没装,
|
|
450
450
|
// 与是不是从我们界面发起无关(终端里同样复现)。首次失败后自动带 `--legacy-peer-deps` 重试一次:
|
package/lib/services/probe.js
CHANGED
|
@@ -19,7 +19,7 @@ const probeFail = () => ({ ok: false, ms: null, status: null });
|
|
|
19
19
|
* 代理受限或吊销服务不可达时整个握手直接失败(CRYPT_E_NO_REVOCATION_CHECK),
|
|
20
20
|
* 表现为 `curl: (35) schannel: next InitializeSecurityContext failed` —— 而 Node/npm/
|
|
21
21
|
* 浏览器各有自己的 TLS 实现,照常可用,用户只会看到「插件市场打不开」。
|
|
22
|
-
* --ssl-revoke-best-effort(curl ≥ 7.70
|
|
22
|
+
* --ssl-revoke-best-effort(curl ≥ 7.70)把吊销检查降级为尽力而为,避免它拦死握手;
|
|
23
23
|
* 其它平台后端(SecureTransport / OpenSSL)没有这个强制行为,不加参数。
|
|
24
24
|
* 运行时读 process.platform(而非模块级常量),便于测试覆盖两条分支。
|
|
25
25
|
*/
|
|
@@ -77,7 +77,7 @@ function macSystemProxy() {
|
|
|
77
77
|
* `http=host:port;https=host:port`(按协议分别配置)。
|
|
78
78
|
* 字符类必须同时排除 `/` 与 `=`:只排除 `=` 时,带 scheme 的值会让「主机名」吞掉 `//`,
|
|
79
79
|
* 拼出 `http:////127.0.0.1:10793` 这种立刻失败的非法地址 —— 代理本来是通的,却把诊断、
|
|
80
|
-
* 目录拉取、npm/git
|
|
80
|
+
* 目录拉取、npm/git 预检全判成「不可达」。
|
|
81
81
|
*/
|
|
82
82
|
export function parseWinProxyServer(stdout) {
|
|
83
83
|
const m = /([^:\s=/]+):(\d+)/.exec(stdout);
|
|
@@ -20,7 +20,7 @@ export function readProfileArg(fallback = 'web') {
|
|
|
20
20
|
* - Electron 桌面宿主:不传 `--profile`,而是把 profile 目录作为位置参数传入
|
|
21
21
|
* (`… <dshRoot> <home>\profiles\desktop …`)。
|
|
22
22
|
* 桌面端只认 `--profile` 会永远落回 fallback,把路由/日志/安装全挂到错误的 profile 上
|
|
23
|
-
* (日志写进 profiles/web/hub.log
|
|
23
|
+
* (日志写进 profiles/web/hub.log、市场必然「插件数据加载失败」)。
|
|
24
24
|
* 位置参数按「父目录名为 profiles」精确识别 profile 目录,避免误取其它位置参数。
|
|
25
25
|
*/
|
|
26
26
|
export function profileFromArgv(argv, fallback = 'web') {
|
|
@@ -11,6 +11,9 @@ export interface WebServerService {
|
|
|
11
11
|
}
|
|
12
12
|
/** Read non-official dependencies installed into one profile. */
|
|
13
13
|
export declare function readInstalled(profile: string): Record<string, string>;
|
|
14
|
+
/** POST mutations are only accepted from the local web server origin or the desktop host page.
|
|
15
|
+
* Exported for tests (tests/routes.test.ts) — the origin check is the whole CSRF defence. */
|
|
16
|
+
export declare function isSameOrigin(request: IncomingMessage): boolean;
|
|
14
17
|
/**
|
|
15
18
|
* Register the Plugin Hub API on the host web server and return a disposer.
|
|
16
19
|
* @param webServer - DSH web server service.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** npm arborist 内部崩溃特征:`Cannot read properties of null (reading 'edgesOut')`。 */
|
|
2
2
|
export declare const NPM_CRASH_EDGES_OUT_RE: RegExp;
|
|
3
|
-
/**
|
|
3
|
+
/** 低于该版本(该缺陷在 npm 11.6.0 修复)遇到 edgesOut 崩溃即判「本机 npm 版本过低」。 */
|
|
4
4
|
export declare const NPM_MIN_VERSION: number[];
|
|
5
5
|
/** 读本机 npm 版本(major.minor.patch);npm 不在 PATH 或执行失败/超时时返回 null(不妄下结论)。 */
|
|
6
6
|
export declare function npmVersionOf(env: NodeJS.ProcessEnv | undefined): [number, number, number] | null;
|
|
@@ -18,7 +18,7 @@ export type ProbeFailReason = 'tlsRevocation';
|
|
|
18
18
|
* 代理受限或吊销服务不可达时整个握手直接失败(CRYPT_E_NO_REVOCATION_CHECK),
|
|
19
19
|
* 表现为 `curl: (35) schannel: next InitializeSecurityContext failed` —— 而 Node/npm/
|
|
20
20
|
* 浏览器各有自己的 TLS 实现,照常可用,用户只会看到「插件市场打不开」。
|
|
21
|
-
* --ssl-revoke-best-effort(curl ≥ 7.70
|
|
21
|
+
* --ssl-revoke-best-effort(curl ≥ 7.70)把吊销检查降级为尽力而为,避免它拦死握手;
|
|
22
22
|
* 其它平台后端(SecureTransport / OpenSSL)没有这个强制行为,不加参数。
|
|
23
23
|
* 运行时读 process.platform(而非模块级常量),便于测试覆盖两条分支。
|
|
24
24
|
*/
|
|
@@ -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
|
+
* 目录拉取、npm/git 预检全判成「不可达」。
|
|
37
37
|
*/
|
|
38
38
|
export declare function parseWinProxyServer(stdout: string): string | null;
|
|
39
39
|
/**
|
|
@@ -6,7 +6,7 @@ export declare function readProfileArg(fallback?: string): string;
|
|
|
6
6
|
* - Electron 桌面宿主:不传 `--profile`,而是把 profile 目录作为位置参数传入
|
|
7
7
|
* (`… <dshRoot> <home>\profiles\desktop …`)。
|
|
8
8
|
* 桌面端只认 `--profile` 会永远落回 fallback,把路由/日志/安装全挂到错误的 profile 上
|
|
9
|
-
* (日志写进 profiles/web/hub.log
|
|
9
|
+
* (日志写进 profiles/web/hub.log、市场必然「插件数据加载失败」)。
|
|
10
10
|
* 位置参数按「父目录名为 profiles」精确识别 profile 目录,避免误取其它位置参数。
|
|
11
11
|
*/
|
|
12
12
|
export declare function profileFromArgv(argv: readonly string[], fallback?: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.10",
|
|
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",
|
|
@@ -456,7 +456,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
456
456
|
// 命令行安装:与应用商店同一套确认/进度/结果弹窗 —— 输入 npm 包名 / GitHub 地址 /
|
|
457
457
|
// dsh plugin 命令即装(custom 源,受安全设置三开关控制)。点安装先弹确认窗,
|
|
458
458
|
// 确认后走队列安装(实时进度 + 成功结果视图);目标已安装 → 弹窗转「更新」覆盖重装,
|
|
459
|
-
//
|
|
459
|
+
// 不会撞「already installed」报错。
|
|
460
460
|
// 安全信任开关关掉对应通道 → 卡片禁用并提示去设置打开(onOpenSettings 跳到设置页)。
|
|
461
461
|
enableNpm: hubSettings.enableNpmInstall,
|
|
462
462
|
enableGit: hubSettings.enableGitInstall,
|
|
@@ -50,8 +50,8 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
|
|
|
50
50
|
? h('span', { className: styles.verified }, t('verified'))
|
|
51
51
|
: null,
|
|
52
52
|
),
|
|
53
|
-
// 描述始终显示:站点英文数据缺翻译时 description
|
|
54
|
-
//
|
|
53
|
+
// 描述始终显示:站点英文数据缺翻译时 description 回退成中文也照常展示 ——
|
|
54
|
+
// 描述是了解插件的第一入口,语言不一致也不该省略。
|
|
55
55
|
p.description ? h('p', { className: styles.desc }, p.description) : null,
|
|
56
56
|
(p.topics?.length ?? 0) > 0
|
|
57
57
|
? h('div', { className: styles.topics },
|
|
@@ -225,8 +225,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
225
225
|
className: styles.failCopy,
|
|
226
226
|
onClick: () => onCopy(r.message),
|
|
227
227
|
}, t('failCopy')),
|
|
228
|
-
//
|
|
229
|
-
// 不再单独占底部一行,卡片只有一行内容,图标文字整体居中不顶头
|
|
228
|
+
// 成功通知:时间戳在行右侧,与图标/文字垂直居中,卡片保持单行内容
|
|
230
229
|
r.ok
|
|
231
230
|
? h('span', {
|
|
232
231
|
className: styles.noticeTime,
|
|
@@ -288,12 +287,12 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
288
287
|
}
|
|
289
288
|
if (kind === 'pnpmWorkspace') {
|
|
290
289
|
// pnpm 拒绝把依赖装到 workspace 根目录(ERR_PNPM_ADDING_TO_ROOT):宿主在 profile
|
|
291
|
-
// 目录调 pnpm add
|
|
292
|
-
// 给 .npmrc 豁免指引,不引导提 Issue
|
|
290
|
+
// 目录调 pnpm add 未声明在根操作,任何插件都会失败 ——
|
|
291
|
+
// 给 .npmrc 豁免指引,不引导提 Issue
|
|
293
292
|
return h('div', { className: styles.failPrepareHint }, t('failPnpmWorkspaceHint'))
|
|
294
293
|
}
|
|
295
294
|
if (kind === 'pnpmStore') {
|
|
296
|
-
// pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile
|
|
295
|
+
// pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile 目录的依赖是
|
|
297
296
|
// 另一大版本 pnpm 装的,当前 pnpm 不认,任何插件装进该 profile 都会失败 ——
|
|
298
297
|
// 给清理重建指引,不引导提 Issue
|
|
299
298
|
return h('div', { className: styles.failPrepareHint }, t('failPnpmStoreHint'))
|
|
@@ -305,17 +304,17 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
305
304
|
}
|
|
306
305
|
if (kind === 'pnpmUnusedPatch') {
|
|
307
306
|
// profile 里留着指向旧版本 dsh-plugin 的补丁声明(ERR_PNPM_UNUSED_PATCH):pnpm
|
|
308
|
-
// 发现补丁没被用上就中止安装,任何插件都装不进来 —— 不引导提 Issue
|
|
307
|
+
// 发现补丁没被用上就中止安装,任何插件都装不进来 —— 不引导提 Issue
|
|
309
308
|
return h('div', { className: styles.failPrepareHint }, t('failPnpmUnusedPatchHint'))
|
|
310
309
|
}
|
|
311
310
|
if (kind === 'fileLocked') {
|
|
312
311
|
// profile 里的文件被其他进程占用(Windows os error 32):通常是宿主或杀毒软件
|
|
313
|
-
// 持有句柄 —— 给出完全退出宿主的指引,不引导提 Issue
|
|
312
|
+
// 持有句柄 —— 给出完全退出宿主的指引,不引导提 Issue
|
|
314
313
|
return h('div', { className: styles.failPrepareHint }, t('failFileLockedHint'))
|
|
315
314
|
}
|
|
316
315
|
if (kind === 'accessDenied') {
|
|
317
316
|
// profile 里的文件被系统拒绝写入(Windows os error 5「拒绝访问」/ EPERM / EACCES):
|
|
318
|
-
// 占用、只读属性、目录 ACL 或杀软拦截 —— 给退出宿主 + 检查权限的指引,不引导提 Issue
|
|
317
|
+
// 占用、只读属性、目录 ACL 或杀软拦截 —— 给退出宿主 + 检查权限的指引,不引导提 Issue
|
|
319
318
|
return h('div', { className: styles.failPrepareHint }, t('failAccessDeniedHint'))
|
|
320
319
|
}
|
|
321
320
|
if (kind === 'fsUnavailable') {
|
|
@@ -323,6 +322,11 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
323
322
|
// 空间不足、盘只读、句柄耗尽,任何插件都装不进来 —— 按子场景给指引,不引导提 Issue
|
|
324
323
|
return h('div', { className: styles.failPrepareHint }, t('failFsUnavailableHint'))
|
|
325
324
|
}
|
|
325
|
+
if (kind === 'originRejected') {
|
|
326
|
+
// 请求来源没通过本地 hub 服务的校验(服务端 403 untrusted origin):请求根本没进
|
|
327
|
+
// 安装流程,与插件无关 —— 引导从本机地址重新打开市场,不引导提 Issue
|
|
328
|
+
return h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint'))
|
|
329
|
+
}
|
|
326
330
|
if (kind === 'network') {
|
|
327
331
|
// 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
|
|
328
332
|
// 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
|
|
@@ -331,7 +335,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
331
335
|
// 「无法访问 …」醒目行:精准告诉用户具体哪个地址连不上(消息里取不到地址就跳过这行)
|
|
332
336
|
(() => { const target = unreachableTargetOf(r.message); return target ? h('div', { className: styles.failNetworkTarget }, t('failNetworkTarget', { url: target })) : null })(),
|
|
333
337
|
// registry 被指向内网/自定义源(tarball 从非官方源下载失败):直接给出换源指引,
|
|
334
|
-
//
|
|
338
|
+
// 否则用户外网是通的会困惑「为什么连不上」
|
|
335
339
|
(() => { const host = registryHostOf(r.message); return host ? h('div', { className: styles.failPrepareHint }, t('failNetworkRegistryHint', { host })) : null })(),
|
|
336
340
|
h('div', { className: styles.failPrepareHint }, t('failNetworkHint')),
|
|
337
341
|
onRunDiagnostics ? h('button', {
|
|
@@ -406,10 +406,10 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
|
|
|
406
406
|
: failureKind === 'pnpmWorkspace'
|
|
407
407
|
// pnpm 拒绝把依赖装到 workspace 根目录(ERR_PNPM_ADDING_TO_ROOT):宿主在 profile 目录
|
|
408
408
|
// 调 pnpm add 时未声明在根操作,任何插件都会失败,不是插件问题 ——
|
|
409
|
-
// 给 .npmrc 豁免指引,不引导提 Issue
|
|
409
|
+
// 给 .npmrc 豁免指引,不引导提 Issue
|
|
410
410
|
? h('div', { className: styles.failPrepareHint }, t('failPnpmWorkspaceHint'))
|
|
411
411
|
: failureKind === 'pnpmStore'
|
|
412
|
-
// pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile
|
|
412
|
+
// pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile 目录的依赖是
|
|
413
413
|
// 另一大版本 pnpm 装的,当前 pnpm 不认,任何插件装进该 profile 都会失败 ——
|
|
414
414
|
// 给清理重建指引,不引导提 Issue
|
|
415
415
|
? h('div', { className: styles.failPrepareHint }, t('failPnpmStoreHint'))
|
|
@@ -419,20 +419,24 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
|
|
|
419
419
|
? h('div', { className: styles.failPrepareHint }, t('failPnpmPolicyHint'))
|
|
420
420
|
: failureKind === 'pnpmUnusedPatch'
|
|
421
421
|
// profile 里留着指向旧版本 dsh-plugin 的补丁声明(ERR_PNPM_UNUSED_PATCH):pnpm 发现
|
|
422
|
-
// 补丁没被用上就中止整次安装,任何插件都装不进来 —— 给删除条目的指引,不引导提 Issue
|
|
422
|
+
// 补丁没被用上就中止整次安装,任何插件都装不进来 —— 给删除条目的指引,不引导提 Issue
|
|
423
423
|
? h('div', { className: styles.failPrepareHint }, t('failPnpmUnusedPatchHint'))
|
|
424
424
|
: failureKind === 'fileLocked'
|
|
425
425
|
// profile 里的文件被其他进程占用(Windows os error 32):pnpm 无法替换被占用的文件,
|
|
426
|
-
// 通常是宿主或杀毒软件持有句柄 —— 给退出宿主的指引,不引导提 Issue
|
|
426
|
+
// 通常是宿主或杀毒软件持有句柄 —— 给退出宿主的指引,不引导提 Issue
|
|
427
427
|
? h('div', { className: styles.failPrepareHint }, t('failFileLockedHint'))
|
|
428
428
|
: failureKind === 'accessDenied'
|
|
429
429
|
// profile 里的文件被系统拒绝写入(Windows os error 5「拒绝访问」/ EPERM / EACCES):
|
|
430
|
-
// 占用、只读属性、目录 ACL 或杀软拦截 —— 同样给退出宿主/检查权限的指引,不引导提 Issue
|
|
430
|
+
// 占用、只读属性、目录 ACL 或杀软拦截 —— 同样给退出宿主/检查权限的指引,不引导提 Issue
|
|
431
431
|
? h('div', { className: styles.failPrepareHint }, t('failAccessDeniedHint'))
|
|
432
432
|
: failureKind === 'fsUnavailable'
|
|
433
433
|
// 本机磁盘写不进去(ENOSPC / EROFS / EMFILE 及 Windows os error 112、19):
|
|
434
434
|
// 空间不足、盘只读、句柄耗尽,任何插件都装不进来 —— 按子场景给指引,不引导提 Issue
|
|
435
435
|
? h('div', { className: styles.failPrepareHint }, t('failFsUnavailableHint'))
|
|
436
|
+
: failureKind === 'originRejected'
|
|
437
|
+
// 请求来源没通过本地 hub 服务的校验(服务端 403 untrusted origin):请求根本没进
|
|
438
|
+
// 安装流程,与插件无关 —— 引导从本机地址重新打开市场,不引导提 Issue
|
|
439
|
+
? h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint'))
|
|
436
440
|
: failureKind === 'network'
|
|
437
441
|
// 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
|
|
438
442
|
// 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
|
|
@@ -441,7 +445,7 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
|
|
|
441
445
|
// 「无法访问 …」醒目行:精准告诉用户具体哪个地址连不上(消息里取不到地址就跳过这行)
|
|
442
446
|
(() => { const target = unreachableTargetOf(message); return target ? h('div', { className: styles.failNetworkTarget }, t('failNetworkTarget', { url: target })) : null })(),
|
|
443
447
|
// registry 被指向内网/自定义源(tarball 从非官方源下载失败):直接给出换源指引,
|
|
444
|
-
//
|
|
448
|
+
// 否则用户外网是通的会困惑「为什么连不上」
|
|
445
449
|
(() => { const host = registryHostOf(message); return host ? h('div', { className: styles.failPrepareHint }, t('failNetworkRegistryHint', { host })) : null })(),
|
|
446
450
|
h('div', { className: styles.failPrepareHint }, t('failNetworkHint')),
|
|
447
451
|
onRunDiagnostics ? h('button', {
|
|
@@ -11,7 +11,7 @@ import type { HubPlugin, LocaleId } from '../types.ts'
|
|
|
11
11
|
import { normalize } from '../logic/normalize.ts'
|
|
12
12
|
|
|
13
13
|
// 目录/统计数据经服务端 /catalog 代理路由获取(curl 子进程注入设置里的代理),
|
|
14
|
-
// 与 npm / git 安装通道同一代理口径 ——
|
|
14
|
+
// 与 npm / git 安装通道同一代理口径 —— 浏览器不直连 api.dsh-plugin.org。
|
|
15
15
|
const PROXY_BASE = '/dsh-plugin-hub/catalog'
|
|
16
16
|
const PLUGINS_URL = (lang: LocaleId) => `${PROXY_BASE}?lang=${lang}`
|
|
17
17
|
const STATS_URL = `${PROXY_BASE}?stats=1`
|
|
@@ -100,7 +100,7 @@ export function useCatalog(lang: LocaleId) {
|
|
|
100
100
|
setHubPlugin(null)
|
|
101
101
|
setStats(null)
|
|
102
102
|
setFailed(false)
|
|
103
|
-
//
|
|
103
|
+
// 此处只拉目录与统计;hub 自身版本由独立 Worker 版本控制中心提供(见 fetchHubUpdate)。
|
|
104
104
|
const load = () => Promise.all([fetchCatalog(lang, controller.signal), fetchStats(controller.signal)])
|
|
105
105
|
load()
|
|
106
106
|
.then(([list, stats]) => {
|
|
@@ -160,7 +160,7 @@ export function useCatalog(lang: LocaleId) {
|
|
|
160
160
|
useEffect(() => { refreshInstalled() }, [])
|
|
161
161
|
|
|
162
162
|
// Hub 自身版本检查走独立 Worker(版本控制中心):发新版后写一次 Worker KV,
|
|
163
|
-
// 所有已装用户立即看到「可更新」+ Markdown
|
|
163
|
+
// 所有已装用户立即看到「可更新」+ Markdown 变更记录,不经过主站目录数据管道。
|
|
164
164
|
// 失败静默降级为 null(徽标不出现,不影响目录本身加载)。
|
|
165
165
|
useEffect(() => {
|
|
166
166
|
let cancelled = false
|
package/src/client/locales.ts
CHANGED
|
@@ -317,6 +317,9 @@ export const zh = {
|
|
|
317
317
|
failNetworkTarget: '无法访问 {url} —— 你的网络连接不通',
|
|
318
318
|
// 「去系统诊断」直达按钮:网络不通提示下方的品牌实底按钮,跳到设置 → 系统诊断跑连通性检测
|
|
319
319
|
failNetworkRunDiag: '去系统诊断检测网络',
|
|
320
|
+
// 请求来源没通过本地 hub 服务校验(服务端 403 untrusted origin):请求根本没进安装流程,
|
|
321
|
+
// 与插件无关 —— 引导从本机地址重新打开市场,不引导提 Issue
|
|
322
|
+
failOriginRejectedHint: '这次安装请求没有通过插件市场的来源校验,被本地服务直接拒绝,所以安装根本没有开始 —— 与插件本身无关。通常是因为市场页面不是从本机地址打开的(比如用了局域网 IP),或宿主页面的来源未被识别。请从本机的插件市场地址(localhost)重新打开页面后重试;桌面端用户重启一次宿主即可。',
|
|
320
323
|
failDshMissingHint: '系统找不到 dsh 命令(dsh 未加入系统 PATH),无法调用安装器执行安装。请确认 DeepSeek Harness 已正确安装、dsh 已加入 PATH(或重新安装),然后重试。这不是插件本身的问题。',
|
|
321
324
|
failGitMissingHint: '系统找不到 git 命令(Git 未安装或未加入系统 PATH),无法从 GitHub 源安装插件。请先安装 Git(如 git-scm.com/downloads)或把 git 加入 PATH,重启 DSH 后重试。这不是插件本身的问题。',
|
|
322
325
|
failPnpmMissingHint: '安装器找到了 dsh,但系统里没有 pnpm 命令(dsh 用它来管理 profile 插件)。请先安装 pnpm(如 npm install -g pnpm),确认 pnpm 已加入 PATH 后重试。这不是插件本身的问题。',
|
|
@@ -687,6 +690,10 @@ export const en = {
|
|
|
687
690
|
failNetworkTarget: 'Cannot reach {url} — your network connection is down',
|
|
688
691
|
// Diagnostics shortcut button under the network-down hint: jumps to Settings → System Diagnostics
|
|
689
692
|
failNetworkRunDiag: 'Run a network diagnostic',
|
|
693
|
+
// The request origin did not pass the local hub service check (403 untrusted origin): the request
|
|
694
|
+
// never reached the install flow — nothing to do with the plugin, so point at the local address
|
|
695
|
+
// and do not offer an issue link
|
|
696
|
+
failOriginRejectedHint: 'This install request did not pass the marketplace origin check and was rejected by the local service, so the install never started — this is not about the plugin itself. It usually means the marketplace page was not opened from a local address (for example a LAN IP), or the host page origin was not recognized. Reopen the marketplace from localhost and retry; on the desktop app, restart the host once.',
|
|
690
697
|
failDshMissingHint: 'The dsh command could not be found (it is not on the system PATH), so the installer could not run. Please make sure DeepSeek Harness is installed correctly and dsh is on your PATH (or reinstall it), then retry. This is not a problem with the plugin itself.',
|
|
691
698
|
failGitMissingHint: 'The git command could not be found (Git is not installed or not on the system PATH), so GitHub-source plugins cannot be installed. Please install Git (e.g. from git-scm.com/downloads) or add git to your PATH, restart DSH, then retry. This is not a problem with the plugin itself.',
|
|
692
699
|
failPnpmMissingHint: 'The installer found dsh, but pnpm is missing on this machine (dsh uses it to manage profile plugins). Please install pnpm first (e.g. npm install -g pnpm), make sure it is on your PATH, then retry. This is not a problem with the plugin itself.',
|
|
@@ -20,7 +20,7 @@ export const HUB_REPO = 'dshplugin/dsh-plugin-hub'
|
|
|
20
20
|
/**
|
|
21
21
|
* Hub 自我更新版本控制(接口中心 Pages:api.dsh-plugin.org,静态 JSON 发布)。
|
|
22
22
|
* 发新版 = 在 api-center 的 releases/ 写发版记录 + 重新部署,hub.json 随之更新,
|
|
23
|
-
*
|
|
23
|
+
* 所有已装用户的「可更新」徽标即可见,不经过主站目录数据管道。
|
|
24
24
|
* 响应:{ version: string | null, publishedAt: string | null, notes: string | {zh,en} | null }
|
|
25
25
|
*/
|
|
26
26
|
export const HUB_UPDATE_URL = 'https://api.dsh-plugin.org/hub.json'
|