dsh-plugin 2.3.2 → 2.3.3
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 +3 -9
- package/README.md +3 -9
- package/client/client.js +367 -362
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +4 -4
- package/lib/http/diagnostics.js +4 -4
- package/lib/http/install.js +13 -13
- package/lib/http/npm-release-age.js +3 -3
- package/lib/http/shared.js +1 -1
- package/lib/services/host-version.js +2 -2
- package/lib/services/install/dsh-compat.js +1 -1
- package/lib/services/install/npm-resolve.js +1 -1
- package/lib/services/install/operation-log.js +3 -3
- package/lib/services/install/preflight.js +1 -1
- package/lib/services/install/registry-fallback.js +3 -3
- package/lib/services/install/release-target.js +2 -2
- package/lib/services/install/task-queue.js +35 -20
- package/lib/services/loader.js +8 -8
- package/lib/services/npm-release-age.js +8 -8
- package/lib/services/probe.js +12 -12
- package/lib/services/profile/installed-versions.js +2 -2
- package/lib/services/profile/profile.js +2 -2
- package/lib/types/services/host-version.d.ts +1 -1
- package/lib/types/services/install/install-types.d.ts +5 -5
- package/lib/types/services/install/operation-log.d.ts +1 -1
- package/lib/types/services/install/preflight.d.ts +1 -1
- package/lib/types/services/install/registry-fallback.d.ts +1 -1
- package/lib/types/services/install/release-target.d.ts +1 -1
- package/lib/types/services/install/task-queue.d.ts +10 -7
- package/lib/types/services/loader.d.ts +5 -5
- package/lib/types/services/npm-release-age.d.ts +5 -5
- package/lib/types/services/probe.d.ts +4 -4
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +29 -29
- package/src/client/components/catalog/CatalogList.tsx +1 -1
- package/src/client/components/catalog/PluginCard.tsx +3 -3
- package/src/client/components/modals/AboutModal.tsx +2 -2
- package/src/client/components/modals/ConfirmDialog.tsx +2 -2
- package/src/client/components/modals/HubUpdateModal.tsx +3 -3
- package/src/client/components/modals/InstalledDetailModal.tsx +1 -1
- package/src/client/components/modals/LogsPathModal.tsx +1 -1
- package/src/client/components/modals/NotificationsModal.tsx +2 -2
- package/src/client/components/modals/modals.tsx +10 -10
- package/src/client/components/views/CustomInstallView.tsx +2 -2
- package/src/client/components/views/InstalledView.tsx +2 -2
- package/src/client/components/views/SettingsView.tsx +3 -3
- package/src/client/data/catalog.ts +1 -1
- package/src/client/data/host.ts +2 -2
- package/src/client/data/hub.ts +3 -3
- package/src/client/hooks/useCatalog.ts +14 -14
- package/src/client/hooks/useSettings.ts +2 -2
- package/src/client/hooks/useTaskQueue.ts +30 -23
- package/src/client/locales.ts +15 -15
- package/src/client/logic/constants.ts +1 -1
- package/src/client/logic/ensure-plugin-css.ts +2 -2
- package/src/client/logic/failures.ts +9 -9
- package/src/client/logic/install-command.ts +1 -2
- package/src/client/logic/installed.ts +1 -1
- package/src/client/logic/release-age.ts +10 -10
- package/src/client/logic/urls.ts +3 -3
- package/src/client/styles/Header.module.css +1 -1
- package/src/client/styles/Modal.module.css +2 -2
- package/src/client/styles/SectionTabs.module.css +1 -1
- package/src/client/styles/tokens.module.css +1 -1
- package/src/client/types.ts +4 -4
- package/src/server/http/catalog.ts +4 -4
- package/src/server/http/diagnostics.ts +4 -4
- package/src/server/http/install.ts +13 -13
- package/src/server/http/npm-release-age.ts +3 -3
- package/src/server/http/shared.ts +1 -1
- package/src/server/services/host-version.ts +2 -2
- package/src/server/services/install/dsh-compat.ts +1 -1
- package/src/server/services/install/install-types.ts +5 -5
- package/src/server/services/install/npm-resolve.ts +1 -1
- package/src/server/services/install/operation-log.ts +3 -3
- package/src/server/services/install/preflight.ts +1 -1
- package/src/server/services/install/registry-fallback.ts +3 -3
- package/src/server/services/install/release-target.ts +2 -2
- package/src/server/services/install/task-queue.ts +40 -26
- package/src/server/services/loader.ts +10 -10
- package/src/server/services/npm-release-age.ts +10 -10
- package/src/server/services/probe.ts +12 -12
- package/src/server/services/profile/installed-versions.ts +2 -2
- package/src/server/services/profile/profile.ts +2 -2
package/lib/services/probe.js
CHANGED
|
@@ -30,13 +30,13 @@ export function curlTlsArgs() {
|
|
|
30
30
|
export function curlRevokeFlagUnsupported(code, stderr) {
|
|
31
31
|
return code === 2 && /ssl-revoke-best-effort/i.test(stderr) && /unknown|unrecognized/i.test(stderr);
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/** 彻底关闭证书吊销检查的保底参数(curl ≥ 7.19),比降级参数覆盖更老的 Windows curl。 */
|
|
34
34
|
const NO_REVOKE_ARG = '--ssl-no-revoke';
|
|
35
35
|
/** stderr 是否指向 Schannel 取不到证书吊销信息(据此归因为环境 TLS 问题,而非网络不通)。 */
|
|
36
36
|
export function tlsRevocationBlocked(stderr) {
|
|
37
37
|
return /CRYPT_E_NO_REVOCATION_CHECK|CRYPT_E_REVOCATION_OFFLINE|0x80092012/i.test(stderr);
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/** 探测/安装同一套代理规则:curl 与 git 子进程都读 HTTP(S)_PROXY 环境变量。 */
|
|
40
40
|
function proxyEnv(proxy) {
|
|
41
41
|
if (proxy === '')
|
|
42
42
|
return { ...process.env };
|
|
@@ -132,11 +132,11 @@ function runCurl(args, env, timeoutMs) {
|
|
|
132
132
|
child.stderr.on('data', (c) => { stderr += c.toString(); });
|
|
133
133
|
child.on('error', () => finish(null));
|
|
134
134
|
child.on('close', (code) => finish(code));
|
|
135
|
-
//
|
|
135
|
+
// 超时保护:curl --max-time 到点自行退出,此处再设一个进程级超时防止悬挂
|
|
136
136
|
const timer = setTimeout(() => { child.kill(); finish(null); }, timeoutMs + 1000);
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
-
/** 带 TLS
|
|
139
|
+
/** 带 TLS 重试的 curl 调用:先按平台参数跑(Windows 带吊销检查降级),
|
|
140
140
|
* 若本机 curl 不认这个参数(curl < 7.70)则去掉参数重跑一次,保证老版本不因此直接失败;
|
|
141
141
|
* 降级后仍被吊销检查拦死(CRYPT_E_NO_REVOCATION_CHECK)时,再用 --ssl-no-revoke
|
|
142
142
|
* 彻底关掉吊销检查跑一次 —— 该参数 curl ≥ 7.19 即存在,覆盖面比降级参数更广,
|
|
@@ -150,7 +150,7 @@ async function curlWithTlsFallback(args, env, timeoutMs) {
|
|
|
150
150
|
}
|
|
151
151
|
if (tlsArgs.length > 0 && tlsRevocationBlocked(run.stderr)) {
|
|
152
152
|
const retry = await runCurl([NO_REVOKE_ARG, ...args], env, timeoutMs);
|
|
153
|
-
//
|
|
153
|
+
// 只在重试本身跑起来(非「不认识该参数」)时采用其结果,否则保留原始失败线索
|
|
154
154
|
if (retry.code !== null && !curlRevokeFlagUnsupported(retry.code, retry.stderr))
|
|
155
155
|
run = retry;
|
|
156
156
|
}
|
|
@@ -158,7 +158,7 @@ async function curlWithTlsFallback(args, env, timeoutMs) {
|
|
|
158
158
|
}
|
|
159
159
|
/**
|
|
160
160
|
* curl 子进程探测:spawn curl -s -w '%{http_code}' 并注入代理 env。
|
|
161
|
-
* curl 读 HTTP_PROXY/HTTPS_PROXY 是原生行为,与 pnpm/git
|
|
161
|
+
* curl 读 HTTP_PROXY/HTTPS_PROXY 是原生行为,与 pnpm/git 安装通道同一套代理规则;
|
|
162
162
|
* 失败(curl 不存在 / 连接失败 / 超时)都归为不可达,全程不抛错。
|
|
163
163
|
* 证书吊销检查受阻(Windows Schannel)另记 reason,供诊断给出对症提示。
|
|
164
164
|
*/
|
|
@@ -219,7 +219,7 @@ export function gitLsRemote(url, proxy, timeoutMs) {
|
|
|
219
219
|
const child = spawn('git', ['ls-remote', '--exit-code', url, 'HEAD'], { env });
|
|
220
220
|
let out = '';
|
|
221
221
|
child.stdout.on('data', (c) => { out += c.toString(); });
|
|
222
|
-
// git
|
|
222
|
+
// git 的报错不原样传递给终端:结果以退出码 + HEAD ref 是否返回为准
|
|
223
223
|
child.stderr.on('data', () => { });
|
|
224
224
|
let done = false;
|
|
225
225
|
const finish = (r) => {
|
|
@@ -235,14 +235,14 @@ export function gitLsRemote(url, proxy, timeoutMs) {
|
|
|
235
235
|
// status 复用为 git 退出码(0 = 克隆握手成功);客户端 git 行不把它显示成 HTTP 码
|
|
236
236
|
finish({ ok, ms: Date.now() - started, status: code });
|
|
237
237
|
});
|
|
238
|
-
//
|
|
238
|
+
// 超时保护:与 curlProbe 同一套规则,防止 git 挂起
|
|
239
239
|
const timer = setTimeout(() => { child.kill(); finish(probeFail()); }, timeoutMs + 1000);
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
243
243
|
* 请求一个 URL 并返回重定向后的最终地址(curl -I -L -w %{url_effective})。
|
|
244
244
|
* 供 GitHub 仓库改名解析使用:对已改名仓库 GitHub 回 301,跟随跳转后的最终地址
|
|
245
|
-
* 即规范地址。与其它 GitHub
|
|
245
|
+
* 即规范地址。与其它 GitHub 访问同一套规则 —— curl 子进程注入代理 env(Node 内置
|
|
246
246
|
* http(s) 读不到系统代理,直连在部分网络下不可达)。
|
|
247
247
|
* 未发生跳转(200/404)、网络失败或 curl 不可用都返回 null,调用方保留原始地址。
|
|
248
248
|
*/
|
|
@@ -257,11 +257,11 @@ export async function finalUrl(url, proxy, timeoutMs) {
|
|
|
257
257
|
return r.code === 0 && final !== '' ? final : null;
|
|
258
258
|
}
|
|
259
259
|
/**
|
|
260
|
-
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入与 TLS
|
|
260
|
+
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入与 TLS 参数重试)。
|
|
261
261
|
* 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,
|
|
262
|
-
* 使「目录数据请求走设置里的代理」与 npm / git
|
|
262
|
+
* 使「目录数据请求走设置里的代理」与 npm / git 安装通道规则一致。
|
|
263
263
|
* 失败(curl 不存在 / 连接失败 / 超时 / 非零退出)时 ok=false,全程不抛错;
|
|
264
|
-
* reason 区分「证书吊销受阻」这类环境 TLS
|
|
264
|
+
* reason 区分「证书吊销受阻」这类环境 TLS 问题,供调用方决定备用策略。
|
|
265
265
|
*/
|
|
266
266
|
export function fetchViaCurl(url, proxy, timeoutMs) {
|
|
267
267
|
// 同样用 -sS:正文之外还要 stderr 里的 TLS 失败线索(被 stderr 收集,不外泄给用户)。
|
|
@@ -65,7 +65,7 @@ export function recordInstalledVersion(profile, repo, version, updatedAt) {
|
|
|
65
65
|
export function recordResolvedNpmPackage(profile, repo, npmPackage) {
|
|
66
66
|
const table = readInstalledVersions(profile);
|
|
67
67
|
const key = repo.toLowerCase();
|
|
68
|
-
// 显式写全字段:这一步发生在安装开始时,版本还不知道 ——
|
|
68
|
+
// 显式写全字段:这一步发生在安装开始时,版本还不知道 —— 但写入磁盘记录必须是完整形状,
|
|
69
69
|
// 否则只带 npmPackage 的残缺记录会在客户端版本比较时崩掉整块界面
|
|
70
70
|
table[key] = {
|
|
71
71
|
version: table[key]?.version ?? '',
|
|
@@ -85,7 +85,7 @@ function writeVersions(profile, table) {
|
|
|
85
85
|
const file = versionsFilePath(profile);
|
|
86
86
|
mkdirSync(dirname(file), { recursive: true });
|
|
87
87
|
// 先写临时文件再原子替换:直接覆盖时进程被杀/磁盘满会留下截断的 JSON,
|
|
88
|
-
//
|
|
88
|
+
// 下次读取整份账本都会失效(更新检测随之失效),而用户看不到任何报错
|
|
89
89
|
const tmp = `${file}.tmp`;
|
|
90
90
|
writeFileSync(tmp, JSON.stringify(table, null, 2) + '\n', 'utf8');
|
|
91
91
|
renameSync(tmp, file);
|
|
@@ -77,7 +77,7 @@ export function githubTarget(repo, ref) {
|
|
|
77
77
|
* 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 必填且只有长选项
|
|
78
78
|
* (官方 CLI 无 -p 简写,省略即报错),支持 `--profile=<name>` 等号形式,命令大小写不敏感。
|
|
79
79
|
* update = 更新已安装目标到最新;remove 不在此列(卸载走 /uninstall 路由,按已装包名直卸)。
|
|
80
|
-
* 与客户端 CustomInstallView
|
|
80
|
+
* 与客户端 CustomInstallView 规则一致。
|
|
81
81
|
*/
|
|
82
82
|
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+(add|update)\s+(.+)$/i;
|
|
83
83
|
export function installTargetOf(value) {
|
|
@@ -88,7 +88,7 @@ export function installTargetOf(value) {
|
|
|
88
88
|
/**
|
|
89
89
|
* npm 全局安装命令(官方 README 的 `npm install -g <pkgs>`,如 dsh-tui 的安装命令)——
|
|
90
90
|
* 提取 -g 之后的包列表。支持 `npm install|i` 与 `-g|--global`;任一包名非法(或格式
|
|
91
|
-
* 不匹配)返回 null,由调用方按「不支持的目标」处理。与客户端 CustomInstallView
|
|
91
|
+
* 不匹配)返回 null,由调用方按「不支持的目标」处理。与客户端 CustomInstallView 同一套规则。
|
|
92
92
|
*/
|
|
93
93
|
const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i;
|
|
94
94
|
export function globalNpmPackagesOf(value) {
|
|
@@ -6,5 +6,5 @@ export declare function versionOf(output: string): string | null;
|
|
|
6
6
|
* Windows 上 pnpm/npm 是 .cmd shim,不走 shell 起不来;参数是固定的 `--version`,没有注入面。 */
|
|
7
7
|
export declare function toolVersion(cmd: string, args: string[]): string | null;
|
|
8
8
|
/** 宿主 dsh 版本:优先 `dsh --version`;拿不到再从入口 `process.argv[1]` 向上找 package.json
|
|
9
|
-
* (dsh 不在 PATH
|
|
9
|
+
* (dsh 不在 PATH 时使用)。探测成功即缓存,失败不缓存以便下次重试。 */
|
|
10
10
|
export declare function hostDshVersion(): string | null;
|
|
@@ -34,14 +34,14 @@ export interface InstallTask {
|
|
|
34
34
|
status: 'pending' | 'running' | 'done' | 'failed' | 'cancelled';
|
|
35
35
|
timedOut: boolean;
|
|
36
36
|
exitCode: number | null;
|
|
37
|
-
/** 0-100 估算进度:解析 pnpm 的 `Progress: resolved…`
|
|
37
|
+
/** 0-100 估算进度:解析 pnpm 的 `Progress: resolved…` 输出;没有进度行时按阶段行估算 */
|
|
38
38
|
progress: number;
|
|
39
39
|
/** 输出行最新在前(消费方展示尾部)。 */
|
|
40
40
|
lines: string[];
|
|
41
|
-
/**
|
|
42
|
-
* 无 loader
|
|
41
|
+
/** 完成后是否仍需宿主重启才生效:仅「热加载失败 / 非 dsh 插件 / 更新(ESM 缓存取不到新代码)/
|
|
42
|
+
* 无 loader」等刷新解决不了的例外场景为 true(弹窗给「立即重启」);其余一律 false。 */
|
|
43
43
|
needsRestart: boolean;
|
|
44
|
-
/**
|
|
44
|
+
/** 完成结果是否只需刷新页面即生效(插件已热加载进运行中 loader,仅剩客户端 UI 待页面重新加载,
|
|
45
45
|
* 网页端与桌面端一致):true 时关闭结果弹窗即自动刷新页面,不给手动刷新按钮。 */
|
|
46
46
|
needsReload?: boolean;
|
|
47
47
|
/** 自更新成功、但该 profile 的 dsh.profile.bundles 已不含 dsh-plugin:重启后市场不会出现 */
|
|
@@ -75,7 +75,7 @@ export interface QueueItem {
|
|
|
75
75
|
installChannel?: 'npm';
|
|
76
76
|
/** npm 源候选(首选在前):传输层失败时按顺序换源重试 */
|
|
77
77
|
registries?: string[];
|
|
78
|
-
/** 精确安装 spec(Hub
|
|
78
|
+
/** 精确安装 spec(Hub 自我更新时锁定版本) */
|
|
79
79
|
installSpec?: string;
|
|
80
80
|
};
|
|
81
81
|
}
|
|
@@ -18,7 +18,7 @@ export declare function hasSubstantiveOutput(output: string): boolean;
|
|
|
18
18
|
/**
|
|
19
19
|
* 运行期抓取:宿主的 operation 日志只在命令运行期间存在,结束后可能被清理,
|
|
20
20
|
* 退出后再按路径读往往已经拿不到。这里在安装过程中轮询 profile 下最新的
|
|
21
|
-
* `.plugin-manager/logs/<operation>/pnpm.log
|
|
21
|
+
* `.plugin-manager/logs/<operation>/pnpm.log`,用抓到的内容补全。
|
|
22
22
|
* 只认本次运行产生的新文件(mtime 晚于启动基线),避免复述上一次安装的日志。
|
|
23
23
|
*/
|
|
24
24
|
export declare function createOperationLogTap(profileDir: string): {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** 缺 git 的失败标记:前端按 `[git-missing]` 归类并给「安装 git」指引(乱码免疫)。 */
|
|
2
2
|
export declare const GIT_MISSING_DETAIL = "[git-missing] the git CLI was not found on this machine (install git and add it to the PATH, then retry)";
|
|
3
3
|
/** 命令存在性探测:which/where 找 PATH 里的命令。
|
|
4
|
-
* 探测自身异常(which/where 不可用等)返回 true
|
|
4
|
+
* 探测自身异常(which/where 不可用等)返回 true,不拦截安装,交给装后校验处理。 */
|
|
5
5
|
export declare function commandOnPath(cmd: string, env?: NodeJS.ProcessEnv): boolean;
|
|
6
6
|
/** 该安装目标是否需要 git:github: / git+ / git@ 与 owner/repo 简写都要先 clone;
|
|
7
7
|
* release 直链(https://…releases/download/…tgz)由 pnpm 直接下载,不需要 git。
|
|
@@ -4,7 +4,7 @@ export declare function normalizeRegistry(value: string): string;
|
|
|
4
4
|
export declare function isPublicRegistry(value: string): boolean;
|
|
5
5
|
/**
|
|
6
6
|
* 依次尝试的源:显式配置 → 本机 npm 配置 → 官方源。
|
|
7
|
-
* 只有公共源才追加后备(最多 3
|
|
7
|
+
* 只有公共源才追加后备(最多 3 个);关闭换源或源不在白名单时只返回它自己。
|
|
8
8
|
*/
|
|
9
9
|
export declare function registryCandidates(configured: string, userRegistry: string, enabled: boolean): string[];
|
|
10
10
|
/** 本机 npm 实际生效的源(含 profile 目录的 .npmrc);读不到返回空串。 */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 预构建 release 直链的仓库身份解析。部分插件(如 loopx)当初以 GitHub release 的 .tgz
|
|
7
7
|
* 安装过,仓库 HEAD 反而是 monorepo 根、走 git 直装会因缺入口文件失败;老安装记录里留的
|
|
8
8
|
* 就是这种 release 直链。这里把「历史安装目标」还原成所属仓库身份,供已装态读回、队列
|
|
9
|
-
*
|
|
9
|
+
* 去重与展示复用(单一事实来源,避免两端规则漂移)。
|
|
10
10
|
*
|
|
11
11
|
* 只接受固定形态的 HTTPS `github.com/<owner>/<repo>/releases/download/<tag>/<asset>.tgz`:
|
|
12
12
|
* 输入是记录/展示数据、绝非 shell 代码,绝不执行其文本;跨仓跳转、外部主机、凭据、
|
|
@@ -39,7 +39,7 @@ export declare function cancelTask(id: number): boolean;
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function desktopCliBootstrap(entry: string): string | null;
|
|
41
41
|
/**
|
|
42
|
-
* 传给 CLI
|
|
42
|
+
* 传给 CLI 的安装目标:锁定版本优先(Hub 自我更新提示的是已过发布冷却期的版本),
|
|
43
43
|
* 其次 npm 的 add/update 都显式 `@latest`;git 源、全局安装、已带版本/tag 的 spec 保持原样。
|
|
44
44
|
*/
|
|
45
45
|
export declare function npmInstallTargetOf(target: string, opts: {
|
|
@@ -48,6 +48,9 @@ export declare function npmInstallTargetOf(target: string, opts: {
|
|
|
48
48
|
pinned?: string;
|
|
49
49
|
add?: boolean;
|
|
50
50
|
}): string;
|
|
51
|
+
/** 重复卸载:目标已不在 profile 依赖里,pnpm 报「没有这个依赖」。
|
|
52
|
+
* 结果与卸载成功等价 —— 按失败上报会弹出错误并自动提一条不存在的 Issue。 */
|
|
53
|
+
export declare function isAlreadyRemoved(action: 'add' | 'remove' | 'update', output: string): boolean;
|
|
51
54
|
/**
|
|
52
55
|
* 插件变更入队并返回其任务。任务严格串行:队列 worker 只在上一个结束后才启动下一个。
|
|
53
56
|
* 完成前可通过 `getTask(id)` / `activeTask()` 查看进度。
|
|
@@ -62,7 +65,7 @@ export declare function startPluginMutation(options: {
|
|
|
62
65
|
globalNpm?: string[];
|
|
63
66
|
/** 待重启行的展示目标(owner/repo):卸载时用于把 npm 包名映射回仓库名 */
|
|
64
67
|
displayTarget?: string;
|
|
65
|
-
/** 运行中 loader
|
|
68
|
+
/** 运行中 loader:安装成功后热加载新条目、卸载成功后停用旧条目,两类操作都尽量立即生效
|
|
66
69
|
* (缺失时安装/卸载仍需重启) */
|
|
67
70
|
loader?: LoaderHandle;
|
|
68
71
|
/** 入队前已尝试的安装方式:失败提 Issue 时如实展示;实际执行的命令由 spawn 时追加 */
|
|
@@ -72,10 +75,10 @@ export declare function startPluginMutation(options: {
|
|
|
72
75
|
/** 自定义安装入口渠道(NPM 包卡片):任务输出行与系统日志据此溯源「从哪个入口发起」;
|
|
73
76
|
* 目录插件安装不传 */
|
|
74
77
|
installChannel?: 'npm';
|
|
75
|
-
/** npm
|
|
78
|
+
/** npm 源候选(首选在前):传输层失败时按顺序换源重试,空/单个表示不换源 */
|
|
76
79
|
registries?: string[];
|
|
77
|
-
/** 精确安装 spec(如 `dsh-plugin@2.0.5`):Hub
|
|
78
|
-
*
|
|
80
|
+
/** 精确安装 spec(如 `dsh-plugin@2.0.5`):Hub 自我更新提示的是已过发布冷却期的版本,
|
|
81
|
+
* 必须锁定安装,不能再用 @latest 被带回冷却期内的最新版 */
|
|
79
82
|
installSpec?: string;
|
|
80
83
|
}): InstallTask;
|
|
81
84
|
/**
|
|
@@ -102,7 +105,7 @@ export declare function verifyInstalledEntry(profile: string, target: string): {
|
|
|
102
105
|
*/
|
|
103
106
|
export declare function hostSpawnMarker(invocation: Invocation, error: unknown): string | null;
|
|
104
107
|
/**
|
|
105
|
-
* 执行插件变更,带一条恢复路径:当 `add` 因 pnpm 的 allowBuilds
|
|
108
|
+
* 执行插件变更,带一条恢复路径:当 `add` 因 pnpm 的 allowBuilds 限制拦下构建脚本而失败 ——
|
|
106
109
|
* 或是 git 源包自己的 `prepare`(`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`,key 取自打印的
|
|
107
110
|
* key@url 提示),或是某依赖的原生构建(`ERR_PNPM_IGNORED_BUILDS`,key = 列出的 name@version)——
|
|
108
111
|
* 就把确切的 key 写进 profile 的 pnpm-workspace.yaml 并重试一次。
|
|
@@ -117,7 +120,7 @@ export declare function runPluginMutation(options: {
|
|
|
117
120
|
task?: InstallTask;
|
|
118
121
|
/** 待重启行的展示目标(owner/repo):卸载时用于把 npm 包名映射回仓库名 */
|
|
119
122
|
displayTarget?: string;
|
|
120
|
-
/** 运行中 loader
|
|
123
|
+
/** 运行中 loader:安装成功后热加载新条目、卸载成功后停用旧条目(缺失时仍需重启) */
|
|
121
124
|
loader?: LoaderHandle;
|
|
122
125
|
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时执行全局安装,不进任何 profile、无需宿主重启 */
|
|
123
126
|
globalNpm?: string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 运行中 loader 的最小接口:卸载即时停用 /
|
|
2
|
+
* 运行中 loader 的最小接口:卸载即时停用 / 安装即时热加载用。
|
|
3
3
|
* 对应官方 `ctx.loader`(cordis-plugin-loader 的 Loader 服务)的读取、新建与移除面。
|
|
4
4
|
* 移除方式:对匹配条目 live-disable(`entry.update({ disabled: true })`),
|
|
5
5
|
* 而不是 `loader.remove(id)` —— disable 走 Entry.update 的 disabled 分支,不触发
|
|
@@ -28,7 +28,7 @@ export interface LoaderHandle {
|
|
|
28
28
|
id?: string;
|
|
29
29
|
name: string;
|
|
30
30
|
}): Promise<string>;
|
|
31
|
-
/** 按 id
|
|
31
|
+
/** 按 id 解析条目:热加载后确认 fiber 真的起来了(import 失败时条目在、但 fiber 为空)。 */
|
|
32
32
|
resolve(id: string): {
|
|
33
33
|
id: string;
|
|
34
34
|
options?: {
|
|
@@ -59,7 +59,7 @@ export declare function isEntryLoaded(loader: LoaderHandle | undefined, name: st
|
|
|
59
59
|
* - `{ ok: true, live: false }`:宿主从未加载过它(磁盘已干净),无需刷新/重启;
|
|
60
60
|
* - `{ ok: true, live: true }`:曾在 loader 中存活且已 live-disable —— 服务端即时卸载;
|
|
61
61
|
* 带客户端 UI 的插件面板要页面刷新才摘除(由调用方按 `hadClientUi` 决定 needsReload);
|
|
62
|
-
* - `{ ok: false, live: true }`:disable 失败 /
|
|
62
|
+
* - `{ ok: false, live: true }`:disable 失败 / 超时,需宿主重启后清理。
|
|
63
63
|
* 宿主关键包(@deepseek-ai/* 与本插件自身)一律跳过。
|
|
64
64
|
*/
|
|
65
65
|
export declare function removeLoadedEntry(loader: LoaderHandle, name: string): Promise<{
|
|
@@ -67,7 +67,7 @@ export declare function removeLoadedEntry(loader: LoaderHandle, name: string): P
|
|
|
67
67
|
live: boolean;
|
|
68
68
|
}>;
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* 安装成功后把新插件热加载进运行中 loader(等价 profile bundle patch 的
|
|
71
71
|
* `- insert: - name: <pkg>` 行),让插件无需重启宿主即可生效。
|
|
72
72
|
* 做法:在 Loader 根组新建条目并启动 —— Loader.write() 为空实现,不写盘;
|
|
73
73
|
* 重启后由 profile 的 bundles 清单重新挂载,不会重复。
|
|
@@ -78,7 +78,7 @@ export declare function removeLoadedEntry(loader: LoaderHandle, name: string): P
|
|
|
78
78
|
export declare function mountLoadedEntry(loader: LoaderHandle, name: string): Promise<boolean>;
|
|
79
79
|
/**
|
|
80
80
|
* 已安装包是否带客户端 UI(package.json 的 `dsh.client` 声明):client-modules 据它把
|
|
81
|
-
* 客户端 bundle 注入宿主页面。带 UI
|
|
81
|
+
* 客户端 bundle 注入宿主页面。带 UI 的插件热加载后需要页面重新加载才会渲染,
|
|
82
82
|
* 网页端与桌面端一致 —— `__DSH_BOOT__` 注入清单由宿主服务端按请求现场拼装,
|
|
83
83
|
* 壳不缓存,所以桌面端刷新页面同样能拿到新清单,无需重开 App。
|
|
84
84
|
*/
|
|
@@ -2,12 +2,12 @@ export interface NpmRelease {
|
|
|
2
2
|
version: string;
|
|
3
3
|
publishedAt: string;
|
|
4
4
|
}
|
|
5
|
-
/** 最新版 +
|
|
5
|
+
/** 最新版 + 可锁定的安装版本:安装锁定后者,pnpm 才不会回退到旧版本。 */
|
|
6
6
|
export interface NpmReleaseWithInstallable extends NpmRelease {
|
|
7
|
-
/**
|
|
7
|
+
/** 已过发布冷却期的最高稳定版;时间表缺失或没有满足项时为 null(调用方退回不锁定版本)。 */
|
|
8
8
|
installable: NpmRelease | null;
|
|
9
9
|
}
|
|
10
|
-
/** pnpm profile 默认 minimumReleaseAge=1440 分钟;+30 分钟缓冲与客户端 release-age
|
|
10
|
+
/** pnpm profile 默认 minimumReleaseAge=1440 分钟;+30 分钟缓冲与客户端 release-age 判定同一套规则。 */
|
|
11
11
|
export declare const MIN_RELEASE_AGE_MS: number;
|
|
12
12
|
/** 请求体包名数量上限:只为已安装插件按需查询,几十个足矣,封顶防滥用。 */
|
|
13
13
|
export declare const MAX_RELEASE_AGE_PACKAGES = 50;
|
|
@@ -17,11 +17,11 @@ export declare const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
|
17
17
|
/** 从完整 packument 取 dist-tags.latest 对应版本及其发布时间;缺任一字段返回 null。 */
|
|
18
18
|
export declare function latestReleaseOf(packument: unknown): NpmRelease | null;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* 已过发布冷却期的最高稳定版本:安装锁定它,pnpm 不会回退到旧版本。
|
|
21
21
|
* 只看纯 x.y.z(预发布版不参与,避免把用户带到不兼容预览版);时间表缺失或无满足项返回 null。
|
|
22
22
|
*/
|
|
23
23
|
export declare function installableReleaseOf(packument: unknown, now?: number): NpmRelease | null;
|
|
24
24
|
/** 请求体 → 去重后的合法包名列表;非数组 / 超量 / 含非法包名一律 null(拒绝非法输入)。 */
|
|
25
25
|
export declare function parseReleaseAgeRequest(body: unknown): string[] | null;
|
|
26
|
-
/**
|
|
26
|
+
/** 查询一个包的最新版本、发布时间与可锁定的安装版本(成功结果按源缓存 10 分钟);registry 为空回退官方源,失败返回 null。 */
|
|
27
27
|
export declare function lookupReleaseAge(packageName: string, registry: string, proxy: string, timeoutMs?: number): Promise<NpmReleaseWithInstallable | null>;
|
|
@@ -63,17 +63,17 @@ export declare function gitLsRemote(url: string, proxy: string, timeoutMs: numbe
|
|
|
63
63
|
/**
|
|
64
64
|
* 请求一个 URL 并返回重定向后的最终地址(curl -I -L -w %{url_effective})。
|
|
65
65
|
* 供 GitHub 仓库改名解析使用:对已改名仓库 GitHub 回 301,跟随跳转后的最终地址
|
|
66
|
-
* 即规范地址。与其它 GitHub
|
|
66
|
+
* 即规范地址。与其它 GitHub 访问同一套规则 —— curl 子进程注入代理 env(Node 内置
|
|
67
67
|
* http(s) 读不到系统代理,直连在部分网络下不可达)。
|
|
68
68
|
* 未发生跳转(200/404)、网络失败或 curl 不可用都返回 null,调用方保留原始地址。
|
|
69
69
|
*/
|
|
70
70
|
export declare function finalUrl(url: string, proxy: string, timeoutMs: number): Promise<string | null>;
|
|
71
71
|
/**
|
|
72
|
-
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入与 TLS
|
|
72
|
+
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入与 TLS 参数重试)。
|
|
73
73
|
* 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,
|
|
74
|
-
* 使「目录数据请求走设置里的代理」与 npm / git
|
|
74
|
+
* 使「目录数据请求走设置里的代理」与 npm / git 安装通道规则一致。
|
|
75
75
|
* 失败(curl 不存在 / 连接失败 / 超时 / 非零退出)时 ok=false,全程不抛错;
|
|
76
|
-
* reason 区分「证书吊销受阻」这类环境 TLS
|
|
76
|
+
* reason 区分「证书吊销受阻」这类环境 TLS 问题,供调用方决定备用策略。
|
|
77
77
|
*/
|
|
78
78
|
export declare function fetchViaCurl(url: string, proxy: string, timeoutMs: number): Promise<{
|
|
79
79
|
ok: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
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",
|
|
@@ -76,10 +76,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
76
76
|
const [confirmPlugin, setConfirmPlugin] = useState<HubPlugin | null>(null)
|
|
77
77
|
/** 弹窗动作是否为「更新」:已安装插件点「更新」→ 走同一条 add 命令原位覆盖重装,文案区分安装/更新 */
|
|
78
78
|
const [confirmIsUpdate, setConfirmIsUpdate] = useState(false)
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
79
|
+
/** 安装要锁定的精确版本:自我更新提示的版本、新安装的已过冷却期版本都要锁定,
|
|
80
|
+
* 否则 @latest 会被 pnpm 带回冷却期内的旧版。 */
|
|
81
81
|
const [confirmPinVersion, setConfirmPinVersion] = useState<string | null>(null)
|
|
82
|
-
/**
|
|
82
|
+
/** 冷却期查询的进行中序号:只采用最后一次弹窗的查询结果,避免切换插件后旧结果覆盖 */
|
|
83
83
|
const installPinSeq = useRef(0)
|
|
84
84
|
/** 命令行安装确认弹窗:记录待安装的裸目标(npm 包名)——
|
|
85
85
|
* 与应用商店同一套确认/进度/结果弹窗,提供时 plugin 传 null 走 customTarget 模式 */
|
|
@@ -106,12 +106,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
106
106
|
/** 安装/卸载完成后的结果视图:停留弹窗内,点「完成」关闭 */
|
|
107
107
|
const [installDone, setInstallDone] = useState(false)
|
|
108
108
|
const [uninstallDone, setUninstallDone] = useState(false)
|
|
109
|
-
/**
|
|
110
|
-
*
|
|
109
|
+
/** 结果视图是否给「立即重启」:服务端任务终态带出。仅刷新解决不了的例外场景
|
|
110
|
+
* (热加载失败 / 非 dsh 插件 / 更新)为 true;其余(热加载成功、卸载已即时停用)为 false,
|
|
111
111
|
* 结果视图只给「完成」,关闭时自动刷新页面生效 */
|
|
112
112
|
const [installNeedsRestart, setInstallNeedsRestart] = useState(false)
|
|
113
113
|
const [uninstallNeedsRestart, setUninstallNeedsRestart] = useState(false)
|
|
114
|
-
/**
|
|
114
|
+
/** 结果视图是否显示「关闭后自动刷新」说明:插件已热加载进运行中 loader(安装)、或卸载后
|
|
115
115
|
* 插件面板仍残留在当前页面(卸载)。关闭弹窗即整页刷新,不给手动刷新按钮。 */
|
|
116
116
|
const [installNeedsReload, setInstallNeedsReload] = useState(false)
|
|
117
117
|
/** 市场自更新成功、但 profile 的 bundle 列表里已不含 dsh-plugin:结果页提示重新勾选 */
|
|
@@ -135,7 +135,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
135
135
|
return () => { alive = false }
|
|
136
136
|
}, [])
|
|
137
137
|
|
|
138
|
-
//
|
|
138
|
+
// 样式自动恢复:宿主重启/热更可能移除 <style> 标签而模块缓存未失效(factory 不重跑、CSS 不回来)。
|
|
139
139
|
// 挂载与每次切换视图时比对全局 CSS 清单,把缺失的样式补注入回去 —— 用户无需再手动点击恢复。
|
|
140
140
|
useEffect(() => { ensurePluginCss() }, [view])
|
|
141
141
|
|
|
@@ -156,7 +156,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
156
156
|
setInstallNeedsReload(reload)
|
|
157
157
|
setInstallBundleDeselected(bundleDeselected === true)
|
|
158
158
|
if (viaModal) setInstallDone(true)
|
|
159
|
-
// 弹窗外的后台安装:给结果 toast
|
|
159
|
+
// 弹窗外的后台安装:给结果 toast;需要页面重载时(热加载的客户端 UI)读完后自动刷新
|
|
160
160
|
else setToast({
|
|
161
161
|
id: Date.now(),
|
|
162
162
|
kind: reload ? 'installedReload' : needsRestart ? 'done' : 'installedLive',
|
|
@@ -193,8 +193,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
193
193
|
// 失败自动写入通知记录:任务在后台结束时没人盯着也能留痕。
|
|
194
194
|
// 存原始 repo(可能只是裸包名)而非反查结果 —— 反查只服务于提 Issue,
|
|
195
195
|
// 通知中心的展示反查由 NotificationsModal 做、查不到会回退显示原始值,
|
|
196
|
-
// 若这里就丢掉原始值,通知里会连名字都看不到;action
|
|
197
|
-
setNotifications(addFailure({ kind, action: update ? 'update' : 'install', repo: repo ?? '', message, command, attempts }))
|
|
196
|
+
// 若这里就丢掉原始值,通知里会连名字都看不到;action 按任务类型区分「安装/更新/卸载失败」
|
|
197
|
+
setNotifications(addFailure({ kind, action: update ? 'update' : kind === 'uninstall' ? 'uninstall' : 'install', repo: repo ?? '', message, command, attempts }))
|
|
198
198
|
},
|
|
199
199
|
installPlugin: confirmPlugin,
|
|
200
200
|
installCustomTarget: confirmCustomTarget,
|
|
@@ -239,7 +239,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
239
239
|
useEffect(() => {
|
|
240
240
|
if (startupCheckedRef.current) return
|
|
241
241
|
if (!settingsReady || catalog.plugins === null || catalog.failed) return
|
|
242
|
-
//
|
|
242
|
+
// 发布冷却期结果未就绪时先等:否则刚发布的插件会先写进通知中心,冒出装不上的更新提醒
|
|
243
243
|
if (!catalog.releaseAgeReady) return
|
|
244
244
|
startupCheckedRef.current = true
|
|
245
245
|
if (!hubSettings.checkUpdatesOnStart) return
|
|
@@ -276,7 +276,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
276
276
|
return () => window.clearTimeout(timer)
|
|
277
277
|
}, [toast])
|
|
278
278
|
|
|
279
|
-
/** 复制文本到剪贴板,返回是否成功(Clipboard API + 隐藏 textarea
|
|
279
|
+
/** 复制文本到剪贴板,返回是否成功(Clipboard API + 隐藏 textarea 作为后备)。 */
|
|
280
280
|
const doCopy = async (text: string): Promise<boolean> => {
|
|
281
281
|
try {
|
|
282
282
|
await navigator.clipboard.writeText(text)
|
|
@@ -300,7 +300,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
/** 命令行安装目标是否已安装(与服务端 already
|
|
303
|
+
/** 命令行安装目标是否已安装(与服务端 already 判定同一套规则):GitHub 地址按仓库身份匹配
|
|
304
304
|
* (归一化后比对已装项的 repo/spec),npm 包名按依赖 key 直查。命中 → 弹窗转「更新」语义
|
|
305
305
|
* (mode=update 放行覆盖重装),避免重复点击撞 409 报错。 */
|
|
306
306
|
const customTargetInstalled = (raw: string): boolean => {
|
|
@@ -334,8 +334,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
334
334
|
setConfirmPlugin(null)
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
/** 新安装先查「已过 pnpm
|
|
338
|
-
* 否则 pnpm 会把发布未满 24h
|
|
337
|
+
/** 新安装先查「已过 pnpm 发布冷却期」的版本:命令条与执行都锁定它,
|
|
338
|
+
* 否则 pnpm 会把发布未满 24h 的最新版不提示地回退成很旧的版本。查不到则不锁定,由服务端再解析一次。 */
|
|
339
339
|
const resolveInstallPin = (p: HubPlugin) => {
|
|
340
340
|
// 目录下发了官方安装命令的条目(CLI-only / 人工干预)只展示该命令,追加版本会改坏它
|
|
341
341
|
if (p.install?.command !== undefined && p.install.command !== '') return
|
|
@@ -365,7 +365,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
/** 「立即重启」:POST 同源 /restart,宿主进程自杀重启;随后轮询服务恢复后整页刷新。
|
|
368
|
-
*
|
|
368
|
+
* 只在刷新解决不了的例外场景(热加载失败 / 非 dsh 插件 / 更新)出现。
|
|
369
369
|
* 桌面端宿主归应用壳管(服务端回 `desktop: true` 且不重启):不轮询,改提示退出重开。 */
|
|
370
370
|
const requestRestart = async () => {
|
|
371
371
|
if (restarting) return
|
|
@@ -380,7 +380,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
380
380
|
/* 服务已终止,无需处理 */
|
|
381
381
|
}
|
|
382
382
|
if (desktop) {
|
|
383
|
-
//
|
|
383
|
+
// 宿主不会重启,轮询等不到新服务:直接给出正确规则
|
|
384
384
|
setRestarting(false)
|
|
385
385
|
setToast({ id: Date.now(), kind: 'restartDesktop' })
|
|
386
386
|
return
|
|
@@ -409,7 +409,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
const { total, installedName, installedVersion, hasUpdate } = catalog
|
|
412
|
-
// 统计数字:官网 /api/stats.json
|
|
412
|
+
// 统计数字:官网 /api/stats.json 优先;拉取失败时改用已加载列表,避免展示 undefined。
|
|
413
413
|
const statsTotal = catalog.stats?.total ?? total
|
|
414
414
|
const statsVerified = catalog.stats?.verified ?? 0
|
|
415
415
|
|
|
@@ -448,7 +448,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
448
448
|
noticeCount: notifications.length + queue.queue.length + queue.pendingRestarts.length,
|
|
449
449
|
onOpenNotifications: () => {
|
|
450
450
|
// 打开弹窗时补齐更新提醒(受「启动时检查更新」开关约束):同一版本只一条,
|
|
451
|
-
//
|
|
451
|
+
// 清空/删除后重开仍恢复,直到插件更新完成才消失;冷却期查询结果未就绪时先不写,避免提示装不上的更新
|
|
452
452
|
if (hubSettings.checkUpdatesOnStart && catalog.releaseAgeReady) syncUpdateNotices()
|
|
453
453
|
setShowNotifications(true)
|
|
454
454
|
},
|
|
@@ -472,12 +472,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
472
472
|
setInstallDone(false)
|
|
473
473
|
setConfirmIsUpdate(update)
|
|
474
474
|
setConfirmPinVersion(null)
|
|
475
|
-
//
|
|
475
|
+
// 本次弹窗重新开始:作废上一次还没返回的冷却期查询
|
|
476
476
|
++installPinSeq.current
|
|
477
477
|
// 与卸载一致:重置弹窗关联任务,避免新弹窗误匹配到进行中的任务而禁用安装按钮
|
|
478
478
|
queue.clearModalTask()
|
|
479
479
|
setConfirmPlugin(p)
|
|
480
|
-
// 更新走 @latest
|
|
480
|
+
// 更新走 @latest 语义(客户端已按冷却期筛选),只有新安装需要锁定冷却期内可用的版本
|
|
481
481
|
if (!update) resolveInstallPin(p)
|
|
482
482
|
},
|
|
483
483
|
onUninstall: (p) => {
|
|
@@ -511,7 +511,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
511
511
|
setInstallDone(false)
|
|
512
512
|
setConfirmIsUpdate(true)
|
|
513
513
|
setConfirmPinVersion(null)
|
|
514
|
-
//
|
|
514
|
+
// 更新不锁定版本:作废可能还没返回的新安装冷却期查询
|
|
515
515
|
++installPinSeq.current
|
|
516
516
|
queue.clearModalTask()
|
|
517
517
|
setConfirmPlugin(item.plugin)
|
|
@@ -576,7 +576,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
576
576
|
// 暂缓提示显示同一标题但只给「稍后再说」,无更新显示当前版本 + 记录 + 「已是最新」。
|
|
577
577
|
// 版本号与本机版本一律由 hubUpdateState / hubCurrentVersion 决定,不用远端版号冒充本机。
|
|
578
578
|
showHubUpdate && h(HubUpdateModal, {
|
|
579
|
-
//
|
|
579
|
+
// 提示目标是「已过发布冷却期」的版本,可能不是 latest —— 展示它与它的变更记录
|
|
580
580
|
info: catalog.hubUpdateTarget ?? catalog.hubUpdateInfo ?? { version: PLUGIN_VERSION },
|
|
581
581
|
state: catalog.hubUpdateState,
|
|
582
582
|
currentVersion: catalog.hubCurrentVersion,
|
|
@@ -589,9 +589,9 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
589
589
|
if (hubEntry) {
|
|
590
590
|
setInstallDone(false)
|
|
591
591
|
setConfirmIsUpdate(true)
|
|
592
|
-
//
|
|
592
|
+
// 锁定本次提示的版本:最新版可能仍在冷却期内,@latest 会被 pnpm 拒绝
|
|
593
593
|
setConfirmPinVersion(catalog.hubUpdateTarget?.version ?? null)
|
|
594
|
-
//
|
|
594
|
+
// 作废可能还没返回的新安装冷却期查询,避免它覆盖本次锁定的版本
|
|
595
595
|
++installPinSeq.current
|
|
596
596
|
queue.clearModalTask()
|
|
597
597
|
setConfirmPlugin(hubEntry)
|
|
@@ -600,7 +600,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
600
600
|
}),
|
|
601
601
|
// 「关注我们」弹窗:GitHub 图标后按钮点击打开 —— 平台介绍 + 用户反馈群二维码,
|
|
602
602
|
// 内容由 dsh-update Worker 的 /about 接口以 Markdown 形式推送,作者随时可改,非写死。
|
|
603
|
-
// Worker 未推送(info 为 null
|
|
603
|
+
// Worker 未推送(info 为 null)时弹窗仍能打开并展示备用文案,不影响其他功能。
|
|
604
604
|
showAbout && h(AboutModal, {
|
|
605
605
|
info: catalog.hubAboutInfo ?? null,
|
|
606
606
|
lang,
|
|
@@ -701,7 +701,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
701
701
|
needsRestart: installNeedsRestart,
|
|
702
702
|
needsReload: installNeedsReload,
|
|
703
703
|
bundleDeselected: installBundleDeselected,
|
|
704
|
-
//
|
|
704
|
+
// 结果弹窗关闭即生效:安装热加载的插件要页面重载才渲染客户端 UI → 自动刷新,无需用户点按钮
|
|
705
705
|
onClose: () => {
|
|
706
706
|
const reload = installDone && installNeedsReload
|
|
707
707
|
setConfirmPlugin(null)
|
|
@@ -711,7 +711,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
711
711
|
onCopy: () => copyCommand(confirmPlugin, confirmPinVersion ?? undefined),
|
|
712
712
|
onInstall: () => queue.installNow(confirmPlugin, {
|
|
713
713
|
update: confirmIsUpdate,
|
|
714
|
-
//
|
|
714
|
+
// 新安装锁定已过冷却期的版本:查询尚未回来时为空,服务端会自行解析
|
|
715
715
|
pinVersion: confirmPinVersion ?? undefined,
|
|
716
716
|
}),
|
|
717
717
|
// 重启:结果视图「立即重启」也会中断进行中的任务 → 先弹重启确认弹窗
|
|
@@ -856,7 +856,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
856
856
|
onRunDiagnostics: openDiagnostics,
|
|
857
857
|
onClose: () => setErrorMsg(null),
|
|
858
858
|
}),
|
|
859
|
-
//
|
|
859
|
+
// 待重启确认弹窗:已安装列表行内「重启」按钮点击后弹出(例外场景),单个「立即重启」,关闭即视为稍后
|
|
860
860
|
showRestartConfirm && h(RestartConfirmModal, {
|
|
861
861
|
t,
|
|
862
862
|
restarting,
|
|
@@ -28,7 +28,7 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
28
28
|
installedName: (p: HubPlugin) => string | null
|
|
29
29
|
installedVersion: (p: HubPlugin) => string | null
|
|
30
30
|
hasUpdate: (p: HubPlugin) => boolean
|
|
31
|
-
/**
|
|
31
|
+
/** 更新暂缓(新版本还没过冷却期)的可重试时刻(毫秒);无则 null */
|
|
32
32
|
updatePendingUntil: (p: HubPlugin) => number | null
|
|
33
33
|
langKey: LocaleId
|
|
34
34
|
onInstall: (p: HubPlugin, opts?: { update?: boolean }) => void
|
|
@@ -19,9 +19,9 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
|
|
|
19
19
|
installedName: (p: HubPlugin) => string | null
|
|
20
20
|
/** 已安装版本号(安装时从目录信号记录;无 release 的仓库为空,无版本号可展示) */
|
|
21
21
|
installedVersion: (p: HubPlugin) => string | null
|
|
22
|
-
/**
|
|
22
|
+
/** 已安装且目录有更新(有版本比版本、无版本比仓库更新时间);冷却期复核未就绪时为 false */
|
|
23
23
|
hasUpdate: (p: HubPlugin) => boolean
|
|
24
|
-
/**
|
|
24
|
+
/** 更新暂缓(新版本还没过冷却期)的可重试时刻(毫秒);无则 null */
|
|
25
25
|
updatePendingUntil: (p: HubPlugin) => number | null
|
|
26
26
|
t: Translate
|
|
27
27
|
langKey: LocaleId
|
|
@@ -35,7 +35,7 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
|
|
|
35
35
|
const pkg = installedName(p)
|
|
36
36
|
const isInstalled = pkg !== null
|
|
37
37
|
const update = hasUpdate(p)
|
|
38
|
-
//
|
|
38
|
+
// 冷却期未到:更新确实存在但现在装不上,给确切时刻而不是让徽标悄悄消失
|
|
39
39
|
const pendingUntil = isInstalled && !update ? updatePendingUntil(p) : null
|
|
40
40
|
// 版本号展示:已安装 → 当前已装版本(最能反映用户状态);未安装 → 目录最新版本
|
|
41
41
|
const versionShown = isInstalled ? installedVersion(p) : p.version
|