dsh-plugin 1.6.12 → 2.0.0
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 +1 -1
- package/README.md +1 -1
- package/client/client.js +615 -741
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +2 -3
- package/lib/http/diagnostics.js +6 -7
- package/lib/http/install.js +47 -213
- package/lib/http/logs.js +1 -2
- package/lib/http/npm-release-age.js +42 -0
- package/lib/http/routes.js +8 -7
- package/lib/http/settings.js +2 -2
- package/lib/http/shared.js +2 -2
- package/lib/http/task.js +1 -2
- package/lib/index.js +5 -6
- package/lib/services/install/install-types.js +4 -4
- package/lib/services/install/install.js +1 -1
- package/lib/services/install/npm-check.js +3 -2
- package/lib/services/install/npm-resolve.js +9 -96
- package/lib/services/install/package-entry.js +6 -11
- package/lib/services/install/registry-check.js +2 -2
- package/lib/services/install/release-target.js +8 -9
- package/lib/services/install/task-queue.js +40 -48
- package/lib/services/npm-release-age.js +119 -0
- package/lib/services/probe.js +4 -4
- package/lib/services/profile/installed-versions.js +15 -21
- package/lib/services/profile/profile.js +16 -44
- package/lib/services/profile/progress.js +8 -11
- package/lib/services/settings.js +2 -5
- package/lib/types/http/catalog.d.ts +1 -1
- package/lib/types/http/install.d.ts +2 -2
- package/lib/types/http/npm-release-age.d.ts +2 -0
- package/lib/types/http/routes.d.ts +10 -12
- package/lib/types/http/shared.d.ts +5 -6
- package/lib/types/index.d.ts +7 -8
- package/lib/types/services/install/install-types.d.ts +12 -13
- package/lib/types/services/install/install.d.ts +1 -1
- package/lib/types/services/install/npm-resolve.d.ts +4 -16
- package/lib/types/services/install/package-entry.d.ts +6 -11
- package/lib/types/services/install/release-target.d.ts +7 -8
- package/lib/types/services/install/task-queue.d.ts +25 -30
- package/lib/types/services/npm-release-age.d.ts +15 -0
- package/lib/types/services/probe.d.ts +4 -4
- package/lib/types/services/profile/installed-versions.d.ts +5 -7
- package/lib/types/services/profile/profile.d.ts +14 -18
- package/lib/types/services/profile/progress.d.ts +7 -10
- package/lib/types/services/settings.d.ts +0 -4
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +17 -22
- package/src/client/components/catalog/CatalogList.tsx +2 -4
- package/src/client/components/catalog/PluginCard.tsx +2 -3
- package/src/client/components/layout/CatalogControls.tsx +5 -6
- package/src/client/components/layout/CatalogHeader.tsx +1 -2
- package/src/client/components/layout/CategoryTabs.tsx +2 -3
- package/src/client/components/layout/SectionTabs.tsx +3 -5
- package/src/client/components/modals/ConfirmDialog.tsx +4 -6
- package/src/client/components/modals/InstalledDetailModal.tsx +3 -6
- package/src/client/components/modals/LogsModal.tsx +7 -12
- package/src/client/components/modals/NotificationsModal.tsx +35 -16
- package/src/client/components/modals/ProgressView.tsx +3 -4
- package/src/client/components/modals/modals.tsx +35 -15
- package/src/client/components/ui/Dropdown.tsx +4 -6
- package/src/client/components/ui/Toggle.tsx +2 -3
- package/src/client/components/ui/icons.tsx +1 -2
- package/src/client/components/views/CustomInstallView.tsx +20 -178
- package/src/client/components/views/DiagnosticsView.tsx +3 -6
- package/src/client/components/views/InstalledView.tsx +6 -11
- package/src/client/components/views/MarketView.tsx +2 -4
- package/src/client/components/views/SettingsView.tsx +9 -32
- package/src/client/css-modules.d.ts +1 -1
- package/src/client/data/catalog.ts +2 -3
- package/src/client/data/host.ts +26 -3
- package/src/client/data/hub.ts +2 -3
- package/src/client/hooks/useCatalog.ts +79 -24
- package/src/client/hooks/useIncrementalList.ts +3 -5
- package/src/client/hooks/useLanguage.ts +4 -6
- package/src/client/hooks/useSettings.ts +4 -11
- package/src/client/hooks/useTaskQueue.ts +36 -42
- package/src/client/index.tsx +2 -3
- package/src/client/locales.ts +75 -114
- package/src/client/logic/constants.ts +1 -2
- package/src/client/logic/ensure-plugin-css.ts +8 -12
- package/src/client/logic/failures.ts +16 -18
- package/src/client/logic/format.ts +1 -1
- package/src/client/logic/install-command.ts +14 -35
- package/src/client/logic/installed.ts +15 -10
- package/src/client/logic/normalize.ts +3 -3
- package/src/client/logic/release-age.ts +19 -0
- package/src/client/logic/urls.ts +20 -20
- package/src/client/styles/CustomInstallView.module.css +3 -30
- package/src/client/styles/DiagnosticsView.module.css +3 -5
- package/src/client/styles/Dropdown.module.css +2 -4
- package/src/client/styles/Header.module.css +6 -7
- package/src/client/styles/InstalledView.module.css +4 -7
- package/src/client/styles/List.module.css +4 -5
- package/src/client/styles/LogsView.module.css +2 -3
- package/src/client/styles/Modal.module.css +4 -5
- package/src/client/styles/SectionTabs.module.css +2 -4
- package/src/client/styles/SettingsView.module.css +3 -5
- package/src/client/styles/Toggle.module.css +2 -4
- package/src/client/styles/tokens.module.css +14 -18
- package/src/client/types.ts +11 -11
- package/src/server/http/catalog.ts +2 -3
- package/src/server/http/diagnostics.ts +6 -7
- package/src/server/http/install.ts +47 -208
- package/src/server/http/logs.ts +1 -2
- package/src/server/http/npm-release-age.ts +42 -0
- package/src/server/http/routes.ts +12 -12
- package/src/server/http/settings.ts +2 -2
- package/src/server/http/shared.ts +5 -6
- package/src/server/http/task.ts +1 -2
- package/src/server/index.ts +7 -8
- package/src/server/services/install/install-types.ts +12 -13
- package/src/server/services/install/install.ts +0 -1
- package/src/server/services/install/npm-check.ts +3 -2
- package/src/server/services/install/npm-resolve.ts +9 -99
- package/src/server/services/install/package-entry.ts +6 -11
- package/src/server/services/install/registry-check.ts +2 -2
- package/src/server/services/install/release-target.ts +8 -9
- package/src/server/services/install/task-queue.ts +47 -54
- package/src/server/services/npm-release-age.ts +107 -0
- package/src/server/services/probe.ts +4 -4
- package/src/server/services/profile/installed-versions.ts +16 -22
- package/src/server/services/profile/profile.ts +16 -42
- package/src/server/services/profile/progress.ts +8 -11
- package/src/server/services/settings.ts +2 -9
- package/lib/services/install/preflight.js +0 -178
- package/lib/types/services/install/preflight.d.ts +0 -23
- package/src/server/services/install/preflight.ts +0 -184
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* active section's rows. New settings only add rows, new areas only add a
|
|
9
|
-
* sidebar entry.
|
|
6
|
+
* 设置视图:桌面设置式导航 —— 固定左侧栏列出各分组(图标 + 名称,点击切换),
|
|
7
|
+
* 右侧面板展示当前分组的设置行。新增设置只加行,新增区域只加一条侧栏项。
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Logs — system log viewer + storage location
|
|
9
|
+
* 各分组对应本地服务端实际强制的行为:
|
|
10
|
+
* 更新与源 —— 启动检查更新 / npm 镜像 / HTTP 代理(都接进安装命令链路)
|
|
11
|
+
* 安全与信任 —— 受限的自定义安装(服务端 403 门禁)
|
|
12
|
+
* 诊断 —— 实时连通性探测 + 环境快照
|
|
13
|
+
* 日志 —— 系统日志查看器 + 存放位置
|
|
17
14
|
*/
|
|
18
15
|
import { createElement as h, useEffect, useRef, useState } from 'react'
|
|
19
16
|
import type { ChangeEvent, MouseEvent, ReactNode } from 'react'
|
|
@@ -207,10 +204,8 @@ export function SettingsView({ t, settings, update, reset, env, onCopy, openSect
|
|
|
207
204
|
)
|
|
208
205
|
|
|
209
206
|
const securityCard = h('div', { className: styles.card },
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
// DSH 命令框为纯客户端通道:关掉后命令输入卡片禁用并引导去设置打开;
|
|
213
|
-
// 目录内收录的插件安装不受影响(默认全部开启)
|
|
207
|
+
// 安全与信任:命令行安装的开关(仅 NPM 包)—— 服务端按通道门禁 403 拦截
|
|
208
|
+
//(npm 目标吃 enableNpmInstall);目录内收录的插件安装不受影响(默认开启)
|
|
214
209
|
h(SettingRow, {
|
|
215
210
|
title: t('settingsEnableNpm'),
|
|
216
211
|
desc: t('settingsEnableNpmDesc'),
|
|
@@ -220,24 +215,6 @@ export function SettingsView({ t, settings, update, reset, env, onCopy, openSect
|
|
|
220
215
|
title: t('settingsEnableNpm'),
|
|
221
216
|
}),
|
|
222
217
|
}),
|
|
223
|
-
h(SettingRow, {
|
|
224
|
-
title: t('settingsEnableGit'),
|
|
225
|
-
desc: t('settingsEnableGitDesc'),
|
|
226
|
-
children: h(Toggle, {
|
|
227
|
-
checked: settings.enableGitInstall,
|
|
228
|
-
onChange: (v) => update({ enableGitInstall: v }),
|
|
229
|
-
title: t('settingsEnableGit'),
|
|
230
|
-
}),
|
|
231
|
-
}),
|
|
232
|
-
h(SettingRow, {
|
|
233
|
-
title: t('settingsEnableDsh'),
|
|
234
|
-
desc: t('settingsEnableDshDesc'),
|
|
235
|
-
children: h(Toggle, {
|
|
236
|
-
checked: settings.enableDshInstall,
|
|
237
|
-
onChange: (v) => update({ enableDshInstall: v }),
|
|
238
|
-
title: t('settingsEnableDsh'),
|
|
239
|
-
}),
|
|
240
|
-
}),
|
|
241
218
|
)
|
|
242
219
|
|
|
243
220
|
const diagnosticsCard = h('div', { className: styles.card },
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* fetch — the repository is the only place that knows where data lives.
|
|
6
|
+
* 目录数据仓库:从在线 API 拉取插件目录与收录统计并做归一化。
|
|
7
|
+
* 组件不接触 URL 与 fetch —— 这里是唯一知道数据所在地的地方。
|
|
9
8
|
*/
|
|
10
9
|
import type { HubPlugin, LocaleId } from '../types.ts'
|
|
11
10
|
import { normalize } from '../logic/normalize.ts'
|
package/src/client/data/host.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* routes degrades to null / empty tables instead of throwing.
|
|
6
|
+
* 宿主数据仓库:DSH 宿主提供的本地路由(已装插件表 + 环境快照)。
|
|
7
|
+
* 宿主未挂载本插件服务端路由时降级为 null / 空表,而不是抛错。
|
|
9
8
|
*/
|
|
10
9
|
import type { EnvInfo } from '../types.ts'
|
|
11
10
|
import type { InstalledVersionSignal } from '../logic/installed.ts'
|
|
@@ -45,6 +44,30 @@ export async function fetchInstalled(): Promise<HostInstalled | null> {
|
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
/** npm 包最新版本的版本号与真实发布时间(服务端经 registry 查询)。 */
|
|
48
|
+
export interface NpmReleaseInfo {
|
|
49
|
+
version: string
|
|
50
|
+
publishedAt: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 批量查询已安装插件 npm 包的最新版本发布时间(供应链门禁用);失败返回 null(调用方按原逻辑照常提示)。 */
|
|
54
|
+
export async function fetchNpmReleaseAge(packages: string[]): Promise<Record<string, NpmReleaseInfo | null> | null> {
|
|
55
|
+
if (packages.length === 0) return {}
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch('/dsh-plugin-hub/npm-release-age', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'content-type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ packages }),
|
|
61
|
+
cache: 'no-store',
|
|
62
|
+
})
|
|
63
|
+
if (!res.ok) return null
|
|
64
|
+
const data = await res.json() as { packages?: Record<string, NpmReleaseInfo | null> }
|
|
65
|
+
return data.packages ?? null
|
|
66
|
+
} catch {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
let envPromise: Promise<EnvInfo | null> | null = null
|
|
49
72
|
|
|
50
73
|
/** 在系统文件管理器里定位并打开已安装插件的目录(服务端 spawn open,跨平台);失败返回 false。 */
|
package/src/client/data/hub.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* Hub
|
|
7
|
-
*
|
|
8
|
-
* decides how to fall back (no badge, placeholder copy), never throws.
|
|
6
|
+
* Hub 元数据仓库:自我更新检查与「关注我们」内容背后的 Cloudflare Worker 接口。
|
|
7
|
+
* 失败降级为 null —— 由调用方决定如何兜底(不显示徽标、占位文案),绝不抛错。
|
|
9
8
|
*/
|
|
10
9
|
import type { HubAboutInfo, HubUpdateInfo } from '../types.ts'
|
|
11
10
|
import { HUB_ABOUT_URL, HUB_UPDATE_URL } from '../logic/constants.ts'
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 插件中心的数据 + 视图状态。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* state, exposing the derived visible list and per-category counts.
|
|
8
|
+
* 掌管在线数据管道(从 api.dsh-plugin.org 实时拉取)、本地已装插件表,以及
|
|
9
|
+
* 筛选/搜索/排序/安装状态等视图状态,对外暴露派生出的可见列表与各分类计数。
|
|
11
10
|
*/
|
|
12
11
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
13
12
|
import type { HubPlugin, LocaleId } from '../types.ts'
|
|
@@ -16,15 +15,19 @@ import type { HubAboutInfo, HubUpdateInfo, HubUpdateState } from '../types.ts'
|
|
|
16
15
|
import type { SortKey } from '../logic/constants.ts'
|
|
17
16
|
import { fetchCatalog, fetchStats } from '../data/catalog.ts'
|
|
18
17
|
import { fetchHubAbout, fetchHubUpdate } from '../data/hub.ts'
|
|
19
|
-
import { fetchInstalled } from '../data/host.ts'
|
|
18
|
+
import { fetchInstalled, fetchNpmReleaseAge } from '../data/host.ts'
|
|
19
|
+
import { isReleaseTooFresh } from '../logic/release-age.ts'
|
|
20
20
|
import {
|
|
21
|
-
hasUpdateOf, installedItemsOf, installedNameOf, installedVersionOf,
|
|
21
|
+
hasUpdateOf, installedItemsOf, installedNameOf, installedVersionOf, npmPackageOf,
|
|
22
22
|
} from '../logic/installed.ts'
|
|
23
23
|
import type { InstalledItem, InstalledVersionSignal } from '../logic/installed.ts'
|
|
24
24
|
|
|
25
25
|
/** 插件市场自身仓库:DSH Plugin Hub 不显示在目录里(自己不进自己的插件列表) */
|
|
26
26
|
const SELF_REPO = 'dshplugin/dsh-plugin-hub'
|
|
27
27
|
|
|
28
|
+
/** 共享的空门禁集合:无暂缓包时复用,避免每次渲染新建 Set。 */
|
|
29
|
+
const EMPTY_PACKAGES: ReadonlySet<string> = new Set<string>()
|
|
30
|
+
|
|
28
31
|
/** 市场各排序的默认方向:全部按倒序(Star/Fork 多、更新/收录近的在前) */
|
|
29
32
|
const SORT_DEFAULT_DIR: Record<SortKey, 'asc' | 'desc'> = {
|
|
30
33
|
sortStars: 'desc', sortForks: 'desc', sortUpdated: 'desc', sortNewest: 'desc',
|
|
@@ -75,7 +78,7 @@ export function useCatalog(lang: LocaleId) {
|
|
|
75
78
|
/** 当前 profile 已安装插件:npm 包名 -> manifest spec(来自宿主本地路由) */
|
|
76
79
|
const [installed, setInstalled] = useState<Record<string, string>>({})
|
|
77
80
|
/** 安装时记录的目录信号:repo(小写) -> { version, updatedAt }(来自宿主本地路由);
|
|
78
|
-
* npmPackage
|
|
81
|
+
* npmPackage 为安装时记录的包名映射(目录数据未下发时客户端靠它把依赖 key 匹配回仓库) */
|
|
79
82
|
const [versions, setVersions] = useState<Record<string, InstalledVersionSignal>>({})
|
|
80
83
|
/** 每个依赖在系统上的安装目录(profile/node_modules/<包名>),详情视图展示用 */
|
|
81
84
|
const [installPaths, setInstallPaths] = useState<Record<string, string> | null>(null)
|
|
@@ -176,25 +179,83 @@ export function useCatalog(lang: LocaleId) {
|
|
|
176
179
|
return () => { cancelled = true }
|
|
177
180
|
}, [reloadKey])
|
|
178
181
|
|
|
179
|
-
/** 插件是否已安装:匹配 Git spec 中的 owner/repo,或 npm
|
|
182
|
+
/** 插件是否已安装:匹配 Git spec 中的 owner/repo,或 npm 安装的依赖包名;命中返回 npm 包名。 */
|
|
180
183
|
const installedName = (p: HubPlugin): string | null => installedNameOf(p, installed, versions)
|
|
181
184
|
|
|
182
185
|
/** 该插件安装时记录的目录版本(无记录/未安装 → null)。 */
|
|
183
186
|
const installedVersion = (p: HubPlugin): string | null => installedVersionOf(p, versions)
|
|
184
187
|
|
|
185
188
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* (repoUpdatedAt,ISO 字符串字典序 = 时间序),更新时间变新说明有新提交。
|
|
189
|
+
* 供应链门禁状态:包名集合为当前「暂缓提示」的 npm 包(最新版本发布未满门槛)。
|
|
190
|
+
* key 是这批候选包名的指纹,与已查出的 key 一致才算就绪,避免查询未回来时先显示更新。
|
|
189
191
|
*/
|
|
190
|
-
const
|
|
192
|
+
const [releaseAge, setReleaseAge] = useState<{ key: string; held: ReadonlySet<string> }>({ key: '', held: EMPTY_PACKAGES })
|
|
191
193
|
|
|
192
|
-
/**
|
|
193
|
-
const
|
|
194
|
+
/** 已安装项原始列表(未套门禁):先算出来才能收集需要查发布时间的候选包名。 */
|
|
195
|
+
const rawInstalledItems = useMemo<InstalledItem[]>(
|
|
194
196
|
() => installedItemsOf(plugins, installed, versions, installPaths, loadedNames, dshCapableNames),
|
|
195
197
|
[plugins, installed, versions, installPaths, loadedNames, dshCapableNames],
|
|
196
198
|
)
|
|
197
199
|
|
|
200
|
+
/** 门禁候选:有更新、且有 npm 包名(更新走 npm registry)的已安装插件包名。 */
|
|
201
|
+
const releaseAgeCandidates = useMemo(() => {
|
|
202
|
+
const names = new Set<string>()
|
|
203
|
+
for (const item of rawInstalledItems) {
|
|
204
|
+
if (!item.hasUpdate || !item.plugin) continue
|
|
205
|
+
const pkg = npmPackageOf(item.plugin, versions)
|
|
206
|
+
if (pkg !== null) names.add(pkg)
|
|
207
|
+
}
|
|
208
|
+
return [...names].sort()
|
|
209
|
+
}, [rawInstalledItems, versions])
|
|
210
|
+
const releaseAgeKey = releaseAgeCandidates.join('\n')
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (releaseAgeKey === releaseAge.key) return
|
|
214
|
+
if (releaseAgeCandidates.length === 0) {
|
|
215
|
+
setReleaseAge({ key: '', held: EMPTY_PACKAGES })
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
let cancelled = false
|
|
219
|
+
// 批量一次查询;失败/返回 null 时 held 为空 → 按原逻辑照常提示,网络问题不吃掉真实更新
|
|
220
|
+
void fetchNpmReleaseAge(releaseAgeCandidates).then((result) => {
|
|
221
|
+
if (cancelled) return
|
|
222
|
+
const now = Date.now()
|
|
223
|
+
const held = new Set<string>()
|
|
224
|
+
for (const name of releaseAgeCandidates) {
|
|
225
|
+
const info = result?.[name]
|
|
226
|
+
if (info && isReleaseTooFresh(info.publishedAt, now)) held.add(name)
|
|
227
|
+
}
|
|
228
|
+
setReleaseAge({ key: releaseAgeKey, held })
|
|
229
|
+
})
|
|
230
|
+
return () => { cancelled = true }
|
|
231
|
+
}, [releaseAgeKey, releaseAge.key])
|
|
232
|
+
|
|
233
|
+
/** 门禁结果是否已就绪(候选为空也算就绪):未就绪时不写更新提醒,等查询回来再定。 */
|
|
234
|
+
const releaseAgeReady = releaseAgeKey === releaseAge.key
|
|
235
|
+
const heldPackages = releaseAgeReady ? releaseAge.held : EMPTY_PACKAGES
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 是否有更新:仅对已安装插件有意义。
|
|
239
|
+
* 双信号判定——有 release 版本的比版本;无版本(repo 不打 tag)的比仓库最近更新时间
|
|
240
|
+
* (repoUpdatedAt,ISO 字符串字典序 = 时间序),更新时间变新说明有新提交。
|
|
241
|
+
* 另加供应链门禁:npm 包最新版本发布未满门槛时视为暂缓,不打更新标记。
|
|
242
|
+
*/
|
|
243
|
+
const hasUpdate = (p: HubPlugin): boolean => {
|
|
244
|
+
if (!hasUpdateOf(p, installed, versions)) return false
|
|
245
|
+
const pkg = npmPackageOf(p, versions)
|
|
246
|
+
return pkg === null || !heldPackages.has(pkg)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** 已安装项统一列表(目录元数据 + 运行时信息合并):门禁命中的去掉更新标记,驱动「已安装」tab。 */
|
|
250
|
+
const installedItems = useMemo<InstalledItem[]>(() => {
|
|
251
|
+
if (heldPackages.size === 0) return rawInstalledItems
|
|
252
|
+
return rawInstalledItems.map((item) => {
|
|
253
|
+
if (!item.hasUpdate || !item.plugin) return item
|
|
254
|
+
const pkg = npmPackageOf(item.plugin, versions)
|
|
255
|
+
return pkg !== null && heldPackages.has(pkg) ? { ...item, hasUpdate: false } : item
|
|
256
|
+
})
|
|
257
|
+
}, [rawInstalledItems, heldPackages, versions])
|
|
258
|
+
|
|
198
259
|
/**
|
|
199
260
|
* Hub 自身更新状态:Hub 就是当前运行的应用(始终已安装,无需 installedName 命中)。
|
|
200
261
|
* 以 Worker 版本控制中心返回的「最新版本」为准,与当前运行的版本号比对——
|
|
@@ -210,15 +271,8 @@ export function useCatalog(lang: LocaleId) {
|
|
|
210
271
|
const hubUpdateState: HubUpdateState = (() => {
|
|
211
272
|
if (!hubUpdateInfo) return 'none'
|
|
212
273
|
if (hubCurrentVersion === '' || hubUpdateInfo.version === hubCurrentVersion) return 'latest'
|
|
213
|
-
|
|
214
|
-
if (publishedAt)
|
|
215
|
-
const publishedMs = Date.parse(publishedAt)
|
|
216
|
-
if (!Number.isNaN(publishedMs)) {
|
|
217
|
-
// 24 小时 + 30 分钟缓冲(防网络/CDN 边缘时间偏差)
|
|
218
|
-
const MIN_RELEASE_AGE_MS = 24 * 60 * 60 * 1000 + 30 * 60 * 1000
|
|
219
|
-
if (Date.now() - publishedMs < MIN_RELEASE_AGE_MS) return 'pending'
|
|
220
|
-
}
|
|
221
|
-
}
|
|
274
|
+
// 供应链门禁(24h + 30 分钟缓冲)与插件更新同一口径:发布未满门槛的版本暂缓提示
|
|
275
|
+
if (isReleaseTooFresh(hubUpdateInfo.publishedAt, Date.now())) return 'pending'
|
|
222
276
|
return 'available'
|
|
223
277
|
})()
|
|
224
278
|
const hubHasUpdate = hubUpdateState === 'available'
|
|
@@ -252,7 +306,7 @@ export function useCatalog(lang: LocaleId) {
|
|
|
252
306
|
})
|
|
253
307
|
}, [plugins, category, query, sort, sortDir, installed])
|
|
254
308
|
|
|
255
|
-
/**
|
|
309
|
+
/** 分类 chip 上展示的各分类插件计数。 */
|
|
256
310
|
const categoryCounts = useMemo(() => {
|
|
257
311
|
const counts: Record<string, number> = {}
|
|
258
312
|
for (const p of plugins ?? []) {
|
|
@@ -274,6 +328,7 @@ export function useCatalog(lang: LocaleId) {
|
|
|
274
328
|
hasUpdate,
|
|
275
329
|
hubHasUpdate,
|
|
276
330
|
hubUpdateState,
|
|
331
|
+
releaseAgeReady,
|
|
277
332
|
hubCurrentVersion,
|
|
278
333
|
hubUpdateInfo,
|
|
279
334
|
hubAboutInfo,
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The items reference is the list identity — category / search / sort
|
|
10
|
-
* changes produce a new array, which resets the window to the first batch.
|
|
6
|
+
* 增量列表:只渲染(可能很大的)结果集的第一批,末尾哨兵滚入视口时就追加下一批,
|
|
7
|
+
* 让插件市场绝不一次性挂载成千上万张卡片。
|
|
8
|
+
* items 引用即列表身份 —— 分类/搜索/排序变化会产出新数组,窗口随之重置回第一批。
|
|
11
9
|
*/
|
|
12
10
|
import { useEffect, useRef, useState } from 'react'
|
|
13
11
|
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* together instead of drifting apart. Exposes the localized dictionary lookup
|
|
10
|
-
* `t()` plus the zh/en language flag and the /zh/ URL path prefix.
|
|
6
|
+
* 板块的语言状态:Hub 与宿主(系统)locale 共用 —— 头部语言开关通过 setLocale 写入
|
|
7
|
+
* 宿主语言偏好,让宿主界面(左侧菜单、设置弹窗)与 Hub 面板一起切换、不会各说各话。
|
|
8
|
+
* 对外暴露本地化字典查询 `t()`、zh/en 语言标记与 /zh/ URL 路径前缀。
|
|
11
9
|
*/
|
|
12
10
|
import { useEffect, useState } from 'react'
|
|
13
11
|
import type { LocaleId, SectionProps } from '../types.ts'
|
|
@@ -22,7 +20,7 @@ export function useLanguage(locale: SectionProps['locale']) {
|
|
|
22
20
|
useEffect(() => locale.subscribe(() => setRev((r) => r + 1)), [locale])
|
|
23
21
|
const lang: LocaleId = locale.getSnapshot().active
|
|
24
22
|
const langKey: LocaleId = lang === 'en' ? 'en' : 'zh'
|
|
25
|
-
// dsh-plugin.org
|
|
23
|
+
// dsh-plugin.org 的 zh 页面挂在 /zh/ 前缀下,en 在根路径。
|
|
26
24
|
const langPath = langPathOf(lang)
|
|
27
25
|
|
|
28
26
|
// 界面语言跟随宿主(系统)locale;宿主的 t() 绑定宿主 locale,
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the defaults and keeps working (settings simply do not persist).
|
|
6
|
+
* 插件中心设置状态:从宿主本地 /settings 路由加载一次(持久化在
|
|
7
|
+
* ~/.dsh/profiles/<profile>/hub-settings.json),并按乐观方式更新。
|
|
8
|
+
* 每次改动立即 POST 补丁,切换过的开关刷新后仍在;宿主未挂服务端路由时退回默认值
|
|
9
|
+
* 继续可用(设置不持久化而已)。
|
|
11
10
|
*/
|
|
12
11
|
import { useCallback, useEffect, useState } from 'react'
|
|
13
12
|
|
|
@@ -20,10 +19,6 @@ export interface HubSettings {
|
|
|
20
19
|
npmRegistry: string
|
|
21
20
|
/** 启用命令行安装 NPM 包(默认开) */
|
|
22
21
|
enableNpmInstall: boolean
|
|
23
|
-
/** 启用命令行安装 GitHub 源码(默认开) */
|
|
24
|
-
enableGitInstall: boolean
|
|
25
|
-
/** 启用命令行粘贴 dsh plugin 命令安装(默认开) */
|
|
26
|
-
enableDshInstall: boolean
|
|
27
22
|
/** 日志存放位置覆盖(空串 = 默认 ~/.dsh/profiles/<profile>/hub.log) */
|
|
28
23
|
logPath: string
|
|
29
24
|
}
|
|
@@ -33,8 +28,6 @@ export const DEFAULT_SETTINGS: HubSettings = {
|
|
|
33
28
|
proxy: '',
|
|
34
29
|
npmRegistry: '',
|
|
35
30
|
enableNpmInstall: true,
|
|
36
|
-
enableGitInstall: true,
|
|
37
|
-
enableDshInstall: true,
|
|
38
31
|
logPath: '',
|
|
39
32
|
}
|
|
40
33
|
|
|
@@ -3,19 +3,18 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 插件中心的后台安装/卸载任务队列。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* uninstall, cancel) plus the modal task lookups used by the dialogs.
|
|
8
|
+
* 通过轮询 /active 镜像服务端 FIFO 活动队列,对消失的任务用 /status 收尾,并暴露
|
|
9
|
+
* 队列动作(安装、卸载、取消)以及弹窗用的任务查找。
|
|
11
10
|
*/
|
|
12
11
|
import { useEffect, useRef, useState } from 'react'
|
|
13
12
|
import type { HubPlugin, InstallChannel, LocaleId, Translate } from '../types.ts'
|
|
14
13
|
import { executedCommandOf, installCommandOf, installTargetOf, repoFromInstallTarget } from '../logic/install-command.ts'
|
|
15
14
|
import type { InstalledItem } from '../logic/installed.ts'
|
|
16
15
|
|
|
17
|
-
/** 安装/卸载请求超时(ms
|
|
18
|
-
*
|
|
16
|
+
/** 安装/卸载请求超时(ms):服务端入队前要做目录包校验与网络探测(各自有超时,合计可达十几秒),
|
|
17
|
+
* 给足余量;超时主动中止,避免服务端网络挂起时前端永久卡「安装中 0%」。 */
|
|
19
18
|
const REQUEST_TIMEOUT_MS = 60_000
|
|
20
19
|
|
|
21
20
|
/** 客户端任务队列项:镜像服务端 active 任务(running 在前、pending 在后)。 */
|
|
@@ -28,8 +27,8 @@ export interface QueueTask {
|
|
|
28
27
|
desc?: string
|
|
29
28
|
/** 所属插件仓库(owner/repo):失败时错误弹窗据此提供「去仓库反馈」入口;卸载为 null */
|
|
30
29
|
repo: string | null
|
|
31
|
-
/** 自定义安装入口渠道(NPM
|
|
32
|
-
*
|
|
30
|
+
/** 自定义安装入口渠道(NPM 包卡片):失败时错误/通知精准溯源「从哪个入口发起」;
|
|
31
|
+
* 目录插件安装无此字段(非自定义入口)。 */
|
|
33
32
|
channel?: InstallChannel
|
|
34
33
|
/** 实际动作(add/update/remove):成功通知据此区分「安装成功」与「更新成功」。
|
|
35
34
|
* 服务端 /active 带回真实动作;本地乐观入队按 update 标记推断。 */
|
|
@@ -42,11 +41,11 @@ export interface QueueTask {
|
|
|
42
41
|
progress: number
|
|
43
42
|
lines: string[]
|
|
44
43
|
/** 乐观入队条目:点击安装后立即显示「正在安装中」,等 POST 响应回来再换成真实任务 id。
|
|
45
|
-
*
|
|
44
|
+
* 服务端还没登记,轮询合并/消失检测都必须跳过,避免被当作已结束任务收尾。 */
|
|
46
45
|
optimistic?: boolean
|
|
47
|
-
/**
|
|
46
|
+
/** 实际执行的安装命令(随插件数据决定):失败时 issue 预填如实展示 */
|
|
48
47
|
command?: string
|
|
49
|
-
/**
|
|
48
|
+
/** 实际执行/重试过的安装命令(按先后顺序):失败时 issue 预填一并贴给作者 */
|
|
50
49
|
attempts?: string[]
|
|
51
50
|
/** 完成结果是否需要重启才生效(服务端任务终态带出)。默认 true 保底:数据缺失时不静默视为已生效。
|
|
52
51
|
* 仅刷新解决不了的兜底场景(热挂载失败 / 非 dsh 插件 / 更新)为 true,结果视图给「立即重启」;
|
|
@@ -70,7 +69,7 @@ export interface PendingRestart {
|
|
|
70
69
|
|
|
71
70
|
export interface TaskQueueOptions {
|
|
72
71
|
t: Translate
|
|
73
|
-
/** 界面语言(zh/en
|
|
72
|
+
/** 界面语言(zh/en):随安装请求带给服务端,安装前网络探测等错误消息按用户语言提示 */
|
|
74
73
|
langKey?: LocaleId
|
|
75
74
|
refreshInstalled: () => void
|
|
76
75
|
/** 任务成功完成:viaModal 表示任务对应当前打开弹窗(弹窗切结果视图),否则走 Toast;repo 供成功通知记录;
|
|
@@ -79,7 +78,7 @@ export interface TaskQueueOptions {
|
|
|
79
78
|
* update 表示实际执行的是更新(覆盖重装)而非首次安装:通知中心据此显示「更新成功」而非「安装成功」。 */
|
|
80
79
|
onInstallDone: (viaModal: boolean, repo: string | null, needsRestart: boolean, update?: boolean, needsReload?: boolean) => void
|
|
81
80
|
onUninstallDone: (viaModal: boolean, repo: string | null, needsRestart: boolean, needsReload?: boolean) => void
|
|
82
|
-
/** 任务失败:完整输出或兜底文案 + 所属插件仓库 + 操作类型(安装/卸载)+ 实际执行的安装命令(issue 预填用,可缺省)+
|
|
81
|
+
/** 任务失败:完整输出或兜底文案 + 所属插件仓库 + 操作类型(安装/卸载)+ 实际执行的安装命令(issue 预填用,可缺省)+ 实际执行/重试过的安装命令(可缺省)+ 是否更新。 */
|
|
83
82
|
onError: (message: string, repo: string | null, kind: 'install' | 'uninstall', command?: string, attempts?: string[], update?: boolean) => void
|
|
84
83
|
/** 当前打开的安装/卸载弹窗插件:用于在弹窗内匹配进行中的任务。 */
|
|
85
84
|
installPlugin: HubPlugin | null
|
|
@@ -96,14 +95,10 @@ export interface TaskQueueOptions {
|
|
|
96
95
|
resolvePending: (repo: string) => { desc?: string; version?: string } | null
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
/** 入口渠道的 i18n key:NPM 包 / GitHub 源码 / DSH 命令。 */
|
|
100
|
-
const channelKeyOf = (channel: InstallChannel): string =>
|
|
101
|
-
channel === 'git' ? 'installChannelGit' : channel === 'dsh' ? 'installChannelDsh' : 'installChannelNpm'
|
|
102
|
-
|
|
103
98
|
/** 失败消息的入口溯源前缀:自定义安装(带渠道)在错误正文顶部精准指出从哪张卡片发起;
|
|
104
99
|
* 目录插件安装(无渠道)返回 null,错误正文保持原样。 */
|
|
105
100
|
const entryLineOf = (t: Translate, channel: InstallChannel | undefined): string | null =>
|
|
106
|
-
channel ? t('installEntryLine', { c: t(
|
|
101
|
+
channel ? t('installEntryLine', { c: t('installChannelNpm') }) : null
|
|
107
102
|
|
|
108
103
|
export function useTaskQueue(opts: TaskQueueOptions) {
|
|
109
104
|
const {
|
|
@@ -332,13 +327,12 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
332
327
|
lines: Array.isArray(a.lines) ? (a.lines as string[]) : (prevTask?.lines ?? []),
|
|
333
328
|
attempts: Array.isArray(a.attempts) ? (a.attempts as string[]) : (prevTask?.attempts ?? []),
|
|
334
329
|
needsRestart: typeof a.needsRestart === 'boolean' ? a.needsRestart : (prevTask?.needsRestart ?? true),
|
|
335
|
-
// 入口渠道:本地乐观入队已带;服务端 /active
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
: prevTask?.channel,
|
|
330
|
+
// 入口渠道:本地乐观入队已带;服务端 /active 也带回(刷新恢复用),服务端优先。
|
|
331
|
+
// 旧服务端可能回传已废弃的 'git'/'dsh' 渠道,忽略即可、回退本地值。
|
|
332
|
+
channel: a.installChannel === 'npm' ? 'npm' : prevTask?.channel,
|
|
339
333
|
})
|
|
340
334
|
}
|
|
341
|
-
//
|
|
335
|
+
// 保留乐观条目:服务端尚未登记,无真实 id,不能因 active 缺它就被合并吞掉
|
|
342
336
|
const keepOpt = prev.filter((q) => q.optimistic && !next.some((x) => x.id === q.id))
|
|
343
337
|
if (keepOpt.length > 0) next.push(...keepOpt)
|
|
344
338
|
// 保留「已从服务端消失、但尚未收尾」的任务:收尾(settleTask)负责移除。
|
|
@@ -402,8 +396,9 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
402
396
|
lines: Array.isArray(a.lines) ? (a.lines as string[]) : [],
|
|
403
397
|
attempts: Array.isArray((a as { attempts?: unknown }).attempts) ? ((a as { attempts: string[] }).attempts) : [],
|
|
404
398
|
needsRestart: typeof (a as { needsRestart?: unknown }).needsRestart === 'boolean' ? (a as { needsRestart: boolean }).needsRestart : true,
|
|
405
|
-
//
|
|
406
|
-
|
|
399
|
+
// 服务端登记的入口渠道:自定义安装刷新后也能精准溯源(目录安装无此字段)。
|
|
400
|
+
// 旧服务端可能回传已废弃的 'git'/'dsh' 渠道,忽略即可。
|
|
401
|
+
channel: a.installChannel === 'npm' ? 'npm' : undefined,
|
|
407
402
|
}
|
|
408
403
|
})
|
|
409
404
|
if (items.length > 0 || pendingRef.current.length > 0) {
|
|
@@ -411,7 +406,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
411
406
|
// 刷新后恢复的任务同样保持折叠:摘要条提示进行中,点开才看明细/日志
|
|
412
407
|
pollQueue()
|
|
413
408
|
}
|
|
414
|
-
} catch { /*
|
|
409
|
+
} catch { /* 宿主未挂本插件服务端路由 */ }
|
|
415
410
|
}
|
|
416
411
|
void restore()
|
|
417
412
|
return () => { cancelled = true }
|
|
@@ -420,9 +415,9 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
420
415
|
/** 弹窗动作:直接安装。请求宿主本地路由,任务进入服务端队列(FIFO),弹窗内实时显示进度。 */
|
|
421
416
|
/** 安装入队核心:目录插件(catalog,后端走目录白名单)与命令行安装(custom,
|
|
422
417
|
* 受安全开关控制)共用。队列条目一律用仓库名(repo)展示/防重,只有发给后端的
|
|
423
|
-
* 实际安装目标(target
|
|
418
|
+
* 实际安装目标(target)为 npm 包名。 */
|
|
424
419
|
const submitInstall = async (input: {
|
|
425
|
-
/** 发给后端的实际安装目标:npm
|
|
420
|
+
/** 发给后端的实际安装目标:npm 包名 */
|
|
426
421
|
target: string
|
|
427
422
|
/** 队列展示与防重用的仓库名 */
|
|
428
423
|
repo: string
|
|
@@ -435,7 +430,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
435
430
|
update?: boolean
|
|
436
431
|
/** 全局 npm 安装包列表(`npm install -g <pkgs>`):非空时服务端执行全局安装,不进任何 profile */
|
|
437
432
|
globalNpm?: string[]
|
|
438
|
-
/** 自定义安装入口渠道(NPM
|
|
433
|
+
/** 自定义安装入口渠道(NPM 包卡片):随请求上报服务端落日志,
|
|
439
434
|
* 失败弹窗/通知据此精准溯源「从哪个入口发起」;目录安装不传。 */
|
|
440
435
|
channel?: InstallChannel
|
|
441
436
|
}) => {
|
|
@@ -445,7 +440,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
445
440
|
if (submittingRef.current.has(input.repo)) return
|
|
446
441
|
submittingRef.current.add(input.repo)
|
|
447
442
|
// 乐观入队:点击安装立即显示「正在安装中」,不等 POST 响应。
|
|
448
|
-
// 服务端 /install
|
|
443
|
+
// 服务端 /install 会先做校验与网络探测再入队,若等响应才入队列,
|
|
449
444
|
// 用户点完安装立刻去看任务会看不到 —— 只有刷新后 restore 才拉回来。
|
|
450
445
|
// 用负临时 id 占位,响应回来换成真实 task id;失败/出错则移除该占位。
|
|
451
446
|
tempIdRef.current -= 1
|
|
@@ -458,7 +453,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
458
453
|
command: input.command,
|
|
459
454
|
channel: input.channel,
|
|
460
455
|
action: input.update ? 'update' : 'add',
|
|
461
|
-
// 服务端登记后 /active
|
|
456
|
+
// 服务端登记后 /active 会带回真实执行过的命令,先占位空列表
|
|
462
457
|
attempts: [], needsRestart: true,
|
|
463
458
|
}])
|
|
464
459
|
setSubmitting(true)
|
|
@@ -473,9 +468,9 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
473
468
|
display: input.repo,
|
|
474
469
|
source: input.source,
|
|
475
470
|
mode: input.update ? 'update' : undefined,
|
|
476
|
-
//
|
|
471
|
+
// 界面语言带给服务端:安装前网络探测等错误消息按用户当前语言返回(中文界面给中文,英文界面给英文)
|
|
477
472
|
...(langKey ? { lang: langKey } : {}),
|
|
478
|
-
//
|
|
473
|
+
// 安装入口渠道(只可能是 npm):服务端日志/报错提示据此溯源
|
|
479
474
|
...(input.channel ? { installChannel: input.channel } : {}),
|
|
480
475
|
...(input.globalNpm && input.globalNpm.length > 0 ? { globalNpm: input.globalNpm } : {}),
|
|
481
476
|
}),
|
|
@@ -485,7 +480,7 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
485
480
|
if (!data.ok || typeof data.task !== 'number') {
|
|
486
481
|
// 请求层失败(重复安装 409 / 参数错误 / 安全开关 403 等):移除乐观条目 + 完整错误弹窗
|
|
487
482
|
applyQueue((prev) => prev.filter((x) => x.id !== tempId))
|
|
488
|
-
// 同步 400
|
|
483
|
+
// 同步 400(如安装前网络探测拦截)服务端会附上已尝试的安装方式,issue 预填一并展示;
|
|
489
484
|
// 用 || 而非 ?? :error 为空字符串时回退 HTTP 状态,避免复制出来是空白。
|
|
490
485
|
// 自定义安装(带入口渠道):错误正文顶部精准提示「从哪张卡片发起」
|
|
491
486
|
const entry = entryLineOf(t, input.channel)
|
|
@@ -521,12 +516,12 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
521
516
|
}
|
|
522
517
|
}
|
|
523
518
|
|
|
524
|
-
/**
|
|
519
|
+
/** 目录插件安装:安装目标取目录数据的 npm 包名,走 catalog 白名单。 */
|
|
525
520
|
const installNow = async (p: HubPlugin, opts?: { update?: boolean }) => {
|
|
526
521
|
const repo = p.source?.repo ?? ''
|
|
527
522
|
if (!repo) return
|
|
528
|
-
//
|
|
529
|
-
const
|
|
523
|
+
// 目录条目必带 npm 包名:直接用包名安装(用户无感知)。
|
|
524
|
+
const target = installTargetOf(p)
|
|
530
525
|
if (!target) return
|
|
531
526
|
await submitInstall({
|
|
532
527
|
target,
|
|
@@ -540,12 +535,11 @@ export function useTaskQueue(opts: TaskQueueOptions) {
|
|
|
540
535
|
})
|
|
541
536
|
}
|
|
542
537
|
|
|
543
|
-
/** 命令行安装:用户手输 npm
|
|
538
|
+
/** 命令行安装:用户手输 npm 包名,走 custom 源(受安全开关控制)。
|
|
544
539
|
* update=true 时对已安装目标放行覆盖重装(与目录插件「更新」同一语义)。
|
|
545
|
-
* channel=入口渠道(NPM
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* 弹窗目标匹配同口径,npm 包名透传不受影响。 */
|
|
540
|
+
* channel=入口渠道(NPM 包卡片):上报服务端落日志,失败弹窗/通知据此精准溯源。
|
|
541
|
+
* 队列展示/防重一律用归一化身份:与服务端 /active 合并、弹窗目标匹配同口径,
|
|
542
|
+
* npm 包名透传不受影响。 */
|
|
549
543
|
const installCustom = async (raw: string, opts?: { update?: boolean; channel?: InstallChannel }) => {
|
|
550
544
|
const target = raw.trim()
|
|
551
545
|
if (!target) return
|
package/src/client/index.tsx
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* dsh-plugin
|
|
7
|
-
*
|
|
8
|
-
* PluginHubSection; this file only performs the cordis apply wiring.
|
|
6
|
+
* dsh-plugin 客户端入口:通过模块加载器 bundle(见 tsdown.config.ts)把插件中心接入宿主设置。
|
|
7
|
+
* 全部 UI 逻辑都在 PluginHubSection;本文件只做 cordis apply 接线。
|
|
9
8
|
*/
|
|
10
9
|
import { createElement as h } from 'react'
|
|
11
10
|
import type { HubClientContext } from './types.ts'
|