dsh-plugin 1.3.11 → 1.4.1
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 +12 -12
- package/README.md +22 -22
- package/client/client.js +376 -343
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +47 -6
- package/lib/services/install/preflight.js +6 -6
- package/lib/types/services/install/preflight.d.ts +5 -1
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +3 -4
- package/src/client/components/catalog/CatalogList.tsx +2 -2
- package/src/client/components/layout/CatalogHeader.tsx +5 -4
- package/src/client/components/modals/NotificationsModal.tsx +10 -0
- package/src/client/components/modals/modals.tsx +9 -2
- package/src/client/components/ui/icons.tsx +1 -1
- package/src/client/components/views/DiagnosticsView.tsx +6 -4
- package/src/client/components/views/InstalledView.tsx +1 -1
- package/src/client/hooks/useCatalog.ts +15 -1
- package/src/client/hooks/useIncrementalList.ts +1 -1
- package/src/client/hooks/useLanguage.ts +14 -10
- package/src/client/locales.ts +20 -16
- package/src/client/logic/constants.ts +2 -2
- package/src/client/logic/failures.ts +29 -5
- package/src/client/logic/urls.ts +11 -5
- package/src/client/styles/InstalledView.module.css +2 -2
- package/src/client/styles/Modal.module.css +8 -8
- package/src/client/types.ts +4 -3
- package/src/server/http/routes.ts +48 -6
- package/src/server/services/install/preflight.ts +11 -7
package/lib/http/routes.js
CHANGED
|
@@ -78,7 +78,7 @@ function startHost() {
|
|
|
78
78
|
const PROFILE_RE = /^[A-Za-z0-9_-]+$/;
|
|
79
79
|
const BODY_LIMIT_BYTES = 4 * 1024;
|
|
80
80
|
const COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
|
|
81
|
-
/** 目录/统计数据代理的本地缓存时长:1
|
|
81
|
+
/** 目录/统计数据代理的本地缓存时长:1 小时内重复打开插件市场直接读盘,
|
|
82
82
|
* 不重复走 curl 拉远程 —— 重启宿主后首次打开同样秒开(缓存跨重启存活)。 */
|
|
83
83
|
const CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
84
84
|
function catalogCacheFile(profile, key) {
|
|
@@ -121,6 +121,20 @@ export function readInstalled(profile) {
|
|
|
121
121
|
return {};
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
/** 读已装依赖实际落在磁盘上的仓库身份(`owner/repo`):解析 node_modules/<name>/package.json
|
|
125
|
+
* 的 repository 字段。已装 spec 可能是纯版本号(丢了仓库身份),但真实包元数据仍指向来源仓库,
|
|
126
|
+
* 据此可区分「同名依赖其实是同一仓库的残留(应转 update)」还是「跨仓库包名冲突(应报错)」。
|
|
127
|
+
* 读不到 / 解析不出返回 null(交给调用方按冲突处理)。 */
|
|
128
|
+
function installedRepoOf(profile, name) {
|
|
129
|
+
try {
|
|
130
|
+
const pkg = JSON.parse(readFileSync(join(profileDirectory(profile), 'node_modules', name, 'package.json'), 'utf8'));
|
|
131
|
+
const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
|
|
132
|
+
return repo ? githubRepoOf(repo) : null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
124
138
|
/** 宿主 dsh CLI 版本:优先直接跑 `dsh --version`(与 pnpm/npm/git 同款同步探测,1500ms 超时兜底,
|
|
125
139
|
* 绝不挂起)—— 原先从 process.argv[1] 向上找 package.json 猜版本在部分宿主加载方式下取不到,
|
|
126
140
|
* 导致 issue 环境快照 DSH: unknown;拿不到再退回入口 package.json 查找(dsh 不在 PATH 时兜底)。 */
|
|
@@ -517,8 +531,8 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
517
531
|
{ key: 'npm', url: `${registry}/dsh-plugin`, display: settings.npmRegistry !== '' ? registry : '', cmd: `npm view dsh-plugin version --registry ${registry}` },
|
|
518
532
|
// GitHub 行用真实 git 克隆握手(git ls-remote)打 dshplugin/hello-dsh 小仓库:
|
|
519
533
|
// 「网页能打开」和「git 能克隆」是两码事,握手成功才算通道通;小仓库秒级完成,不打 17MB 的 dsh-plugin-hub。
|
|
520
|
-
{ key: 'github', url: 'https://github.com/dshplugin/hello-dsh', display: 'github.com
|
|
521
|
-
{ key: 'catalog', url: 'https://api.dsh-plugin.org/stats.json', display: '
|
|
534
|
+
{ key: 'github', url: 'https://github.com/dshplugin/hello-dsh', display: 'github.com', cmd: 'git ls-remote https://github.com/dshplugin/hello-dsh', git: true },
|
|
535
|
+
{ key: 'catalog', url: 'https://api.dsh-plugin.org/stats.json', display: '', cmd: 'curl -s https://api.dsh-plugin.org/stats.json' },
|
|
522
536
|
];
|
|
523
537
|
// 配置了 HTTP 代理:追加一行代理诊断 —— 用该代理打 github.com(安装通道真实访问的地址),
|
|
524
538
|
// 验证「代理能不能把请求带出去」。与安装同口径:curl 子进程注入该代理 env。
|
|
@@ -620,7 +634,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
620
634
|
const target = isStats
|
|
621
635
|
? 'https://api.dsh-plugin.org/stats.json'
|
|
622
636
|
: `https://api.dsh-plugin.org/plugins.${lang}.json`;
|
|
623
|
-
// 1
|
|
637
|
+
// 1 小时本地缓存:命中直接返回(跨重启生效,插件市场秒开),未命中才走 curl 拉远程
|
|
624
638
|
const cacheFile = catalogCacheFile(profile, isStats ? 'stats' : `plugins-${lang}`);
|
|
625
639
|
const cached = readCatalogCache(cacheFile);
|
|
626
640
|
if (cached !== null) {
|
|
@@ -655,8 +669,9 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
655
669
|
// 任务自动入队:即使已有插件操作在跑也接受请求(FIFO 串行执行),不再 409 拒绝
|
|
656
670
|
try {
|
|
657
671
|
const body = await readJsonBody(request);
|
|
658
|
-
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm
|
|
659
|
-
|
|
672
|
+
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm 对已存在依赖原位覆盖重装)。
|
|
673
|
+
// 后续「同仓库残留」检测可能把 install 就地升级为 update(见包名冲突检测),故用 let。
|
|
674
|
+
let mode = body !== null && typeof body === 'object' && body.mode === 'update'
|
|
660
675
|
? 'update'
|
|
661
676
|
: 'install';
|
|
662
677
|
// installTargetOf 先把完整 DSH 命令(`dsh plugin [--profile <p>] add <target>`)剥成裸目标,
|
|
@@ -829,6 +844,32 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
829
844
|
});
|
|
830
845
|
return;
|
|
831
846
|
}
|
|
847
|
+
// 包名冲突检测(仅 git 通道):git 目标包内声明的 name 若与 profile 已装依赖同名,
|
|
848
|
+
// pnpm 会以该 name 做依赖键撞车,抛出的 CLI 报错晦涩难懂(issue #25:sandbase-harness
|
|
849
|
+
// 声明 managed-agents,但该包名在 registry 上被另一仓库占用,npm 反查搜不到 → git 直装
|
|
850
|
+
// 撞已装同名依赖)。到这里已装依赖要么来源不同仓库、要么 spec 是版本号解析不出仓库身份。
|
|
851
|
+
// 读 node_modules 里已装包的真实 repository 区分两种情况:
|
|
852
|
+
// - 指向同一仓库 → 是「同仓库残留」(spec 丢了仓库身份),就地转 update 覆盖重装(幂等);
|
|
853
|
+
// - 指向别的仓库 / 读不到 → 跨仓库包名冲突,入队前转成明确 409,指导用户先卸载或改装。
|
|
854
|
+
if (preflight.name !== null && repoTarget !== null && target === repoTarget) {
|
|
855
|
+
const declared = preflight.name;
|
|
856
|
+
if (installed[declared] !== undefined) {
|
|
857
|
+
const installedRepo = installedRepoOf(profile, declared);
|
|
858
|
+
if (installedRepo !== null && gitRepo !== null && installedRepo.toLowerCase() === gitRepo.toLowerCase()) {
|
|
859
|
+
// 同名且同源:已装 spec 丢了仓库身份导致的残留,直接按 update 覆盖重装,不打扰用户
|
|
860
|
+
attempts.push(`same-repo residual: \`${declared}\` resolves to \`${installedRepo}\` → reinstall as update`);
|
|
861
|
+
mode = 'update';
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
attempts.push(`package name conflict: \`${gitRepo}\` declares npm name \`${declared}\`, which is already present in the profile and cannot be matched to this repo (that npm name is likely registered by another repository)`);
|
|
865
|
+
const error = lang === 'zh'
|
|
866
|
+
? `already installed: ${declared} —— ${gitRepo} 声明的 npm 包名「${declared}」已存在于当前 profile,且无法匹配到本仓库(该包名在 npm 上可能被其他仓库占用)。请先移除现有的 ${declared}(dsh plugin remove ${declared}),再重新安装;或改装该包名对应的官方 npm 包。`
|
|
867
|
+
: `already installed: ${declared} — ${gitRepo} declares the npm package name "${declared}", which is already present in the profile and cannot be matched to this repo (that npm name is likely registered by another repository). Remove the existing ${declared} dependency (dsh plugin remove ${declared}) first, then retry; or install the official npm package under that name.`;
|
|
868
|
+
sendJson(response, 409, { error, attempts });
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
832
873
|
}
|
|
833
874
|
// 入队前复检(与上方检查同语义,但必须在同步块内):preflight 的 await 期间并发请求
|
|
834
875
|
// 可能已通过上方检查、任务却尚未入队——此处检查与 startPluginMutation 之间无 await,
|
|
@@ -25,7 +25,7 @@ const PREFLIGHT_TIMEOUT_MS = 15_000;
|
|
|
25
25
|
export async function preflightTarget(target) {
|
|
26
26
|
const source = githubRepoOf(target);
|
|
27
27
|
if (source === null)
|
|
28
|
-
return { ok: true, missing: null };
|
|
28
|
+
return { ok: true, missing: null, name: null };
|
|
29
29
|
const [owner, repo] = source.split('/');
|
|
30
30
|
let commit = '';
|
|
31
31
|
try {
|
|
@@ -36,17 +36,17 @@ export async function preflightTarget(target) {
|
|
|
36
36
|
commit = stdout.split(/\s+/)[0] ?? '';
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
39
|
-
return { ok: true, missing: null };
|
|
39
|
+
return { ok: true, missing: null, name: null };
|
|
40
40
|
}
|
|
41
41
|
if (!commit)
|
|
42
|
-
return { ok: true, missing: null };
|
|
42
|
+
return { ok: true, missing: null, name: null };
|
|
43
43
|
const dir = mkdtempSync(join(tmpdir(), 'dsh-preflight-'));
|
|
44
44
|
try {
|
|
45
45
|
const tar = join(dir, 'pkg.tar.gz');
|
|
46
46
|
await download(`https://codeload.github.com/${owner}/${repo}/tar.gz/${commit}`, tar);
|
|
47
47
|
const meta = await readTarJson(tar, 'package/package.json');
|
|
48
48
|
if (meta === null)
|
|
49
|
-
return { ok: true, missing: null };
|
|
49
|
+
return { ok: true, missing: null, name: null };
|
|
50
50
|
const dot = meta.exports?.['.'];
|
|
51
51
|
const resolved = typeof dot === 'string'
|
|
52
52
|
? dot
|
|
@@ -55,10 +55,10 @@ export async function preflightTarget(target) {
|
|
|
55
55
|
: undefined;
|
|
56
56
|
const entry = typeof resolved === 'string' ? resolved : typeof meta.main === 'string' ? meta.main : 'index.js';
|
|
57
57
|
const inTar = await hasTarEntry(tar, `package/${entry}`);
|
|
58
|
-
return { ok: inTar, missing: inTar ? null : entry };
|
|
58
|
+
return { ok: inTar, missing: inTar ? null : entry, name: typeof meta.name === 'string' ? meta.name : null };
|
|
59
59
|
}
|
|
60
60
|
catch {
|
|
61
|
-
return { ok: true, missing: null };
|
|
61
|
+
return { ok: true, missing: null, name: null };
|
|
62
62
|
}
|
|
63
63
|
finally {
|
|
64
64
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
/** 预检结果:ok=false 表示分发改入口文件缺失,missing
|
|
1
|
+
/** 预检结果:ok=false 表示分发改入口文件缺失,missing 为该文件在包内的相对路径;
|
|
2
|
+
* name 为 git 分发包 package.json 声明的包名(非 git 目标/无法确定时为 null),
|
|
3
|
+
* 供安装路由做「包名冲突」检测 —— 同名包名已被其他来源占用时,pnpm 装前必然撞车,
|
|
4
|
+
* 需要把晦涩的 CLI 报错转成明确的拦截。 */
|
|
2
5
|
export interface PreflightResult {
|
|
3
6
|
ok: boolean;
|
|
4
7
|
missing: string | null;
|
|
8
|
+
name: string | null;
|
|
5
9
|
}
|
|
6
10
|
export declare function preflightTarget(target: string): Promise<PreflightResult>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 4000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):4000+ 人工精选社区插件,每日更新。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -46,7 +46,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
46
46
|
/** 设置状态:服务端 hub-settings.json 持久化,本地乐观更新即时生效 */
|
|
47
47
|
const { settings: hubSettings, ready: settingsReady, update: updateSettings, reset: resetSettings } = useSettings()
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/** 一级导航:插件市场 / 已安装 / 自定义安装 / 设置 */
|
|
50
50
|
const [view, setView] = useState<SectionView>('market')
|
|
51
51
|
/** 外部跳转设置分组的一次性信号(错误弹窗「去系统诊断」→ 设置 → 系统诊断):
|
|
52
52
|
* 传给 SettingsView 消费后即清空,避免下次进设置被强制带跳 */
|
|
@@ -195,7 +195,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// 启动更新策略:目录数据与设置都就绪后触发一次 —— 开启「启动时检查更新」→ 发现可更新插件
|
|
198
|
-
//
|
|
198
|
+
// 即写入通知中心(每条一条,点击可去更新),不弹窗打扰,由通知中心红点提示。
|
|
199
199
|
// 绝不自动安装:是否更新完全由用户决定。
|
|
200
200
|
// ref 守卫保证只跑一次,后续目录刷新/设置变化不会重复打扰。
|
|
201
201
|
const startupCheckedRef = useRef(false)
|
|
@@ -204,8 +204,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
204
204
|
if (!settingsReady || catalog.plugins === null || catalog.failed) return
|
|
205
205
|
startupCheckedRef.current = true
|
|
206
206
|
if (!hubSettings.checkUpdatesOnStart) return
|
|
207
|
-
|
|
208
|
-
if (added > 0) setToast({ id: Date.now(), kind: 'updates', n: added })
|
|
207
|
+
syncUpdateNotices()
|
|
209
208
|
}, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, queue])
|
|
210
209
|
|
|
211
210
|
// 信任/卸载/更新说明弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { createElement as h, useEffect, useRef } from 'react'
|
|
12
12
|
import styles from '../../styles/List.module.css'
|
|
13
13
|
import type { HubPlugin, LocaleId, Translate } from '../../types.ts'
|
|
14
|
-
import {
|
|
14
|
+
import { siteUrl } from '../../logic/urls.ts'
|
|
15
15
|
import { useIncrementalList } from '../../hooks/useIncrementalList.ts'
|
|
16
16
|
import { PluginCard } from './PluginCard.tsx'
|
|
17
17
|
|
|
@@ -82,7 +82,7 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
82
82
|
plugins !== null && !failed && h('div', { className: styles.footer },
|
|
83
83
|
h('a', {
|
|
84
84
|
className: styles.footLink,
|
|
85
|
-
href:
|
|
85
|
+
href: siteUrl(langPath),
|
|
86
86
|
target: '_blank',
|
|
87
87
|
rel: 'noopener noreferrer',
|
|
88
88
|
}, t('browseAll', { n: total })),
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { createElement as h, Fragment } from 'react'
|
|
10
10
|
import styles from '../../styles/Header.module.css'
|
|
11
11
|
import type { Translate } from '../../types.ts'
|
|
12
|
-
import { GITHUB_URL, PLUGIN_VERSION
|
|
12
|
+
import { GITHUB_URL, PLUGIN_VERSION } from '../../logic/constants.ts'
|
|
13
|
+
import { siteUrl } from '../../logic/urls.ts'
|
|
13
14
|
import { GitHubIcon, LogoIcon } from '../ui/icons.tsx'
|
|
14
15
|
|
|
15
16
|
export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggleLang, hubUpdate, onVersionClick, onAboutClick }: {
|
|
@@ -31,7 +32,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
31
32
|
h('div', { className: styles.headerTitleRow },
|
|
32
33
|
h('a', {
|
|
33
34
|
className: styles.brandTitle,
|
|
34
|
-
href:
|
|
35
|
+
href: siteUrl(langPath),
|
|
35
36
|
target: '_blank',
|
|
36
37
|
rel: 'noopener noreferrer',
|
|
37
38
|
title: t('openHint'),
|
|
@@ -86,7 +87,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
86
87
|
// 第二行:副标题仍是官网链接,点击跳 dsh-plugin.org
|
|
87
88
|
h('a', {
|
|
88
89
|
className: styles.taglineLink,
|
|
89
|
-
href:
|
|
90
|
+
href: siteUrl(langPath),
|
|
90
91
|
target: '_blank',
|
|
91
92
|
rel: 'noopener noreferrer',
|
|
92
93
|
title: t('openHint'),
|
|
@@ -96,7 +97,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
96
97
|
),
|
|
97
98
|
h('a', {
|
|
98
99
|
className: styles.adBanner,
|
|
99
|
-
href:
|
|
100
|
+
href: siteUrl(langPath),
|
|
100
101
|
target: '_blank',
|
|
101
102
|
rel: 'noopener noreferrer',
|
|
102
103
|
},
|
|
@@ -271,11 +271,21 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
271
271
|
// 本机 DeepSeek Harness 未正确安装或 dsh 不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
|
|
272
272
|
return h('div', { className: styles.failPrepareHint }, t('failDshMissingHint'))
|
|
273
273
|
}
|
|
274
|
+
if (kind === 'gitMissing') {
|
|
275
|
+
// 找不到 git 命令(Win「'git' is not recognized」/ POSIX「git: command not found」/ spawn ENOENT):
|
|
276
|
+
// 本机 Git 未安装或不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
|
|
277
|
+
return h('div', { className: styles.failPrepareHint }, t('failGitMissingHint'))
|
|
278
|
+
}
|
|
274
279
|
if (kind === 'pnpmMissing') {
|
|
275
280
|
// 找到了 dsh 但缺 pnpm(dsh 用它管理 profile 插件;dsh 报 pnpm not found on PATH):
|
|
276
281
|
// 本机缺 pnpm,不是插件问题 —— 给安装指引,不引导提 Issue
|
|
277
282
|
return h('div', { className: styles.failPrepareHint }, t('failPnpmMissingHint'))
|
|
278
283
|
}
|
|
284
|
+
if (kind === 'npmMissing') {
|
|
285
|
+
// 全局 npm 安装通道 spawn 的 npm 找不到:本机 npm 未安装或不在 PATH ——
|
|
286
|
+
// 给安装指引,不引导提 Issue
|
|
287
|
+
return h('div', { className: styles.failPrepareHint }, t('failNpmMissingHint'))
|
|
288
|
+
}
|
|
279
289
|
if (kind === 'pnpmStore') {
|
|
280
290
|
// pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile 目录旧依赖是
|
|
281
291
|
// 另一大版本 pnpm 装的,当前 pnpm 不认,任何插件装进该 profile 都会失败 ——
|
|
@@ -391,10 +391,18 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
|
|
|
391
391
|
// 找不到 dsh 命令(Win「不是内部或外部命令」/ POSIX「command not found」/ spawn ENOENT):
|
|
392
392
|
// 本机 DeepSeek Harness 未正确安装或 dsh 不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
|
|
393
393
|
? h('div', { className: styles.failPrepareHint }, t('failDshMissingHint'))
|
|
394
|
+
: failureKind === 'gitMissing'
|
|
395
|
+
// 找不到 git 命令(Win「'git' is not recognized」/ POSIX「git: command not found」/ spawn ENOENT):
|
|
396
|
+
// 本机 Git 未安装或不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
|
|
397
|
+
? h('div', { className: styles.failPrepareHint }, t('failGitMissingHint'))
|
|
394
398
|
: failureKind === 'pnpmMissing'
|
|
395
399
|
// 找到了 dsh 但缺 pnpm(dsh 用它管理 profile 插件;dsh 报 pnpm not found on PATH):
|
|
396
400
|
// 本机缺 pnpm,不是插件问题 —— 给安装指引,不引导提 Issue
|
|
397
401
|
? h('div', { className: styles.failPrepareHint }, t('failPnpmMissingHint'))
|
|
402
|
+
: failureKind === 'npmMissing'
|
|
403
|
+
// 全局 npm 安装通道 spawn 的 npm 找不到(Win「'npm' is not recognized」/ POSIX
|
|
404
|
+
// 「npm: command not found」/ spawn ENOENT):本机 npm 未安装或不在 PATH —— 给安装指引,不引导提 Issue
|
|
405
|
+
? h('div', { className: styles.failPrepareHint }, t('failNpmMissingHint'))
|
|
398
406
|
: failureKind === 'pnpmStore'
|
|
399
407
|
// pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile 目录旧依赖是
|
|
400
408
|
// 另一大版本 pnpm 装的,当前 pnpm 不认,任何插件装进该 profile 都会失败 ——
|
|
@@ -456,8 +464,7 @@ export function Toast({ toast, t }: { toast: ToastState; t: Translate }) {
|
|
|
456
464
|
: toast.kind === 'fail' ? t('installFail')
|
|
457
465
|
: toast.kind === 'removed' ? t('uninstallDone')
|
|
458
466
|
: toast.kind === 'revealFail' ? t('openFolderFail')
|
|
459
|
-
:
|
|
460
|
-
: t('uninstallFail')
|
|
467
|
+
: t('uninstallFail')
|
|
461
468
|
const fail = toast.kind === 'fail' || toast.kind === 'removeFail' || toast.kind === 'revealFail'
|
|
462
469
|
return h('div', {
|
|
463
470
|
key: toast.id,
|
|
@@ -179,7 +179,7 @@ export function SortArrowIcon({ up }: { up: boolean }) {
|
|
|
179
179
|
}))
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
/**
|
|
182
|
+
/** 插件市场图标:店面货架(stroke 继承 currentColor),一级导航「插件市场」tab 用。 */
|
|
183
183
|
export function MarketIcon() {
|
|
184
184
|
return h('svg', {
|
|
185
185
|
viewBox: '0 0 24 24',
|
|
@@ -42,8 +42,8 @@ interface RowState {
|
|
|
42
42
|
const INITIAL_ROWS: RowState[] = [
|
|
43
43
|
// npm 行 display 初始置空:设置里未配置镜像时显示「未配置(跟随本机)」,探测结果回来再用服务端 display 覆盖
|
|
44
44
|
{ key: 'npm', display: '', nameKey: 'diagNpm', status: 'idle', ms: null, statusCode: null },
|
|
45
|
-
{ key: 'github', display: 'github.com
|
|
46
|
-
{ key: 'catalog', display: '
|
|
45
|
+
{ key: 'github', display: 'github.com', nameKey: 'diagGithub', status: 'idle', ms: null, statusCode: null },
|
|
46
|
+
{ key: 'catalog', display: '', nameKey: 'diagCatalog', status: 'idle', ms: null, statusCode: null },
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
export function DiagnosticsView({ t, env, proxy, onCopy }: {
|
|
@@ -172,8 +172,10 @@ export function DiagnosticsView({ t, env, proxy, onCopy }: {
|
|
|
172
172
|
title: t('diagRecheck'),
|
|
173
173
|
},
|
|
174
174
|
h('span', { className: styles.name }, t(r.nameKey)),
|
|
175
|
-
// npm 行未配置镜像:display 为空 →
|
|
176
|
-
|
|
175
|
+
// npm 行未配置镜像:display 为空 → 显示「未配置(跟随本机)」,不误导成官方源;
|
|
176
|
+
// 目录站点行不暴露域名/API 地址,固定显示「插件市场」这一友好称呼
|
|
177
|
+
h('span', { className: styles.display },
|
|
178
|
+
r.key === 'catalog' ? t('diagCatalogTarget') : (r.display !== '' ? r.display : t('diagNpmUnset'))),
|
|
177
179
|
h('span', { className: styles.meta }, r.status === 'ok' && r.ms !== null ? `${r.ms} ms` : ''),
|
|
178
180
|
badge,
|
|
179
181
|
)
|
|
@@ -111,7 +111,7 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail,
|
|
|
111
111
|
? h('span', { className: styles.updateBadge, title: t('updateAvailableHint') }, t('updateAvailable'))
|
|
112
112
|
: null,
|
|
113
113
|
),
|
|
114
|
-
// 来源标签列(行尾、操作按钮前,右对齐固定):目录插件
|
|
114
|
+
// 来源标签列(行尾、操作按钮前,右对齐固定):目录插件 →「插件市场」(品牌色),
|
|
115
115
|
// 命令行安装 →「手动安装」(琥珀色);两词 4 字等宽,所有条目标签同列对齐
|
|
116
116
|
h('span', {
|
|
117
117
|
className: isCustom
|
|
@@ -200,11 +200,25 @@ export function useCatalog(lang: LocaleId) {
|
|
|
200
200
|
* 以 Worker 版本控制中心返回的「最新版本」为准,与当前运行的版本号比对——
|
|
201
201
|
* 构建时注入的 PLUGIN_VERSION 是运行 bundle 的真实版本;测试/异常场景缺失时
|
|
202
202
|
* 回退到安装时记录的版本。版本号不等即新版(版本号只在发版时变更,绝无降级场景)。
|
|
203
|
+
*
|
|
204
|
+
* 另加 pnpm 供应链安全门槛:新版本发布还不满 24 小时(+30 分钟缓冲)时,pnpm 会静默
|
|
205
|
+
* 回退到旧版本,此时若提示「可更新」用户点了也更新不到。publishedAt 由接口中心按 npm
|
|
206
|
+
* registry 真实发布时间下发(同源),据此判断最准。
|
|
203
207
|
*/
|
|
204
208
|
const hubHasUpdate = (() => {
|
|
205
209
|
if (!hubUpdateInfo) return false
|
|
206
210
|
const current = PLUGIN_VERSION || versions[HUB_REPO]?.version || null
|
|
207
|
-
|
|
211
|
+
if (current === null || hubUpdateInfo.version === current) return false
|
|
212
|
+
const publishedAt = hubUpdateInfo.publishedAt
|
|
213
|
+
if (publishedAt) {
|
|
214
|
+
const publishedMs = Date.parse(publishedAt)
|
|
215
|
+
if (!Number.isNaN(publishedMs)) {
|
|
216
|
+
// 24 小时 + 30 分钟缓冲(防网络/CDN 边缘时间偏差)
|
|
217
|
+
const MIN_RELEASE_AGE_MS = 24 * 60 * 60 * 1000 + 30 * 60 * 1000
|
|
218
|
+
if (Date.now() - publishedMs < MIN_RELEASE_AGE_MS) return false
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return true
|
|
208
222
|
})()
|
|
209
223
|
|
|
210
224
|
/** 当前分类下的插件(「全部」时为整个目录)。 */
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Incremental list: renders only the first batch of a (potentially huge)
|
|
7
7
|
* result set and appends the next batch whenever the end sentinel scrolls
|
|
8
|
-
* into view, so the plugin
|
|
8
|
+
* into view, so the plugin market never mounts thousands of cards at once.
|
|
9
9
|
* The items reference is the list identity — category / search / sort
|
|
10
10
|
* changes produce a new array, which resets the window to the first batch.
|
|
11
11
|
*/
|
|
@@ -3,25 +3,29 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* Language state for the section:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Language state for the section: the Hub shares the host (system) locale —
|
|
7
|
+
* the header language toggle writes the host locale preference via setLocale,
|
|
8
|
+
* so the host chrome (left sidebar, settings dialog) and the Hub panel switch
|
|
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.
|
|
10
11
|
*/
|
|
11
|
-
import { useState } from 'react'
|
|
12
|
+
import { useEffect, useState } from 'react'
|
|
12
13
|
import type { LocaleId, SectionProps } from '../types.ts'
|
|
13
14
|
import { en, zh } from '../locales.ts'
|
|
14
15
|
import { langPathOf } from '../logic/urls.ts'
|
|
15
16
|
|
|
16
17
|
export function useLanguage(locale: SectionProps['locale']) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// 界面语言 = 宿主(系统)语言:右上角按钮切换时直接写入宿主 locale 偏好,
|
|
19
|
+
// 宿主左侧菜单/设置弹窗与 Hub 面板一起切换,不存在 Hub 内部语言与宿主语言脱节的情况。
|
|
20
|
+
// subscribe 订阅宿主语言变化并触发重渲染,保证 Hub 界面随时跟随宿主。
|
|
21
|
+
const [, setRev] = useState(0)
|
|
22
|
+
useEffect(() => locale.subscribe(() => setRev((r) => r + 1)), [locale])
|
|
23
|
+
const lang: LocaleId = locale.getSnapshot().active
|
|
20
24
|
const langKey: LocaleId = lang === 'en' ? 'en' : 'zh'
|
|
21
25
|
// dsh-plugin.org keeps zh pages under the /zh/ prefix; en is the root.
|
|
22
26
|
const langPath = langPathOf(lang)
|
|
23
27
|
|
|
24
|
-
// 界面语言跟随宿主(系统)locale
|
|
28
|
+
// 界面语言跟随宿主(系统)locale;宿主的 t() 绑定宿主 locale,
|
|
25
29
|
// 这里基于本地字典自建翻译函数,与宿主 locale 保持一致。
|
|
26
30
|
// (settings.section 的导航 label 仍在 apply 里用宿主 t(),跟随宿主语言。)
|
|
27
31
|
const dict: Record<string, string> = langKey === 'en' ? en : zh
|
|
@@ -35,6 +39,6 @@ export function useLanguage(locale: SectionProps['locale']) {
|
|
|
35
39
|
langKey,
|
|
36
40
|
langPath,
|
|
37
41
|
t,
|
|
38
|
-
toggleLang: () =>
|
|
42
|
+
toggleLang: () => locale.setLocale(lang === 'en' ? 'zh' : 'en'),
|
|
39
43
|
}
|
|
40
44
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* zh/en dictionaries for the DSH Plugin Hub settings section.
|
|
7
7
|
*/
|
|
8
8
|
export const zh = {
|
|
9
|
-
nav: '
|
|
9
|
+
nav: '插件市场',
|
|
10
10
|
title: 'DSH Plugin Hub',
|
|
11
11
|
// 收录/精选数量由组件从官网 /api/stats.json 实时拉取填充({total}/{verified}),不写死
|
|
12
|
-
tagline: 'DSH Plugin
|
|
12
|
+
tagline: 'DSH Plugin 插件市场:面向 DeepSeek Harness 插件收录平台,人工审验、来源可溯,免费安装',
|
|
13
13
|
adBadge: '推荐',
|
|
14
14
|
// 紫色广告横幅:突出数量(显眼)+ 更新节奏,与 tagline 职责分开、不重复
|
|
15
|
-
ad: 'DSH Plugin
|
|
15
|
+
ad: 'DSH Plugin 插件市场:收录 {total} 款插件,{verified} 款人工精选验证,每日更新',
|
|
16
16
|
search: '搜索插件名称、描述、标签…',
|
|
17
17
|
all: '全部',
|
|
18
18
|
sortStars: 'Star',
|
|
@@ -26,8 +26,8 @@ export const zh = {
|
|
|
26
26
|
loading: '正在加载插件数据…',
|
|
27
27
|
failed: '插件数据加载失败',
|
|
28
28
|
// 目录拉不出来时的精准提示:按「是否配置了代理」区分,引导排查网络 / 代理
|
|
29
|
-
failedDescNoProxy: '
|
|
30
|
-
failedDescProxy: '你的网络似乎不通,或设置里的 HTTP
|
|
29
|
+
failedDescNoProxy: '你的网络似乎不通,无法获取插件目录数据。请检查网络连接后重试;或点击右上角按钮在浏览器中打开插件市场。',
|
|
30
|
+
failedDescProxy: '你的网络似乎不通,或设置里的 HTTP 代理地址不可达,无法获取插件目录数据。请检查代理设置与网络连接后重试;或点击右上角按钮在浏览器中打开插件市场。',
|
|
31
31
|
retry: '重试',
|
|
32
32
|
install: '安装',
|
|
33
33
|
// AI 识别可能需要命令行辅助的插件(webInstallable=false):仍可尝试一键安装,失败时复制命令去 dsh 终端
|
|
@@ -35,8 +35,8 @@ export const zh = {
|
|
|
35
35
|
failedCopyHint: '安装失败。请复制上方安装命令到 dsh 终端手动执行(原生依赖构建脚本可能需要人工放行,如 pnpm approve-builds)',
|
|
36
36
|
toastCopied: '安装命令已复制,去 dsh 终端粘贴即可安装',
|
|
37
37
|
installed: '已安装',
|
|
38
|
-
//
|
|
39
|
-
viewMarket: '
|
|
38
|
+
// 一级导航:插件市场 / 已安装 / 自定义安装 / 设置
|
|
39
|
+
viewMarket: '插件市场',
|
|
40
40
|
viewInstalled: '已安装',
|
|
41
41
|
viewCustom: '自定义安装',
|
|
42
42
|
viewSettings: '设置',
|
|
@@ -76,8 +76,8 @@ export const zh = {
|
|
|
76
76
|
customLabel: '自定义安装',
|
|
77
77
|
installedSearch: '搜索已安装的插件…',
|
|
78
78
|
installedFilterCatalog: '目录插件',
|
|
79
|
-
hubInstall: '
|
|
80
|
-
hubInstallHint: '
|
|
79
|
+
hubInstall: '插件市场',
|
|
80
|
+
hubInstallHint: '来自插件市场收录的目录插件',
|
|
81
81
|
manualInstall: '手动安装',
|
|
82
82
|
manualInstallHint: '非目录收录,通过命令行手动安装',
|
|
83
83
|
filterByLabel: '来源',
|
|
@@ -109,7 +109,7 @@ export const zh = {
|
|
|
109
109
|
settingsUpdate: '更新设置',
|
|
110
110
|
settingsUpdateDesc: '更新检查方式与安装走到的网络通道',
|
|
111
111
|
settingsCheckOnStart: '启动时检查更新',
|
|
112
|
-
settingsCheckOnStartDesc: '
|
|
112
|
+
settingsCheckOnStartDesc: '打开插件市场时检查已安装插件是否有新版,发现更新会提示',
|
|
113
113
|
settingsProxy: 'HTTP 代理',
|
|
114
114
|
settingsProxyDesc: 'npm / git / 目录数据请求统一走该代理;留空直连',
|
|
115
115
|
// 代理连通性实时探测:输入停笔后提示检测结果(不通仍可保存,先填地址后开代理也合法)
|
|
@@ -192,6 +192,7 @@ export const zh = {
|
|
|
192
192
|
diagNpmUnset: '未配置(跟随本机 npm 配置)',
|
|
193
193
|
diagGithub: 'GitHub',
|
|
194
194
|
diagCatalog: '目录站点',
|
|
195
|
+
diagCatalogTarget: '插件市场',
|
|
195
196
|
diagProxy: 'HTTP 代理',
|
|
196
197
|
diagOk: 'OK',
|
|
197
198
|
diagChecking: '检测中…',
|
|
@@ -212,8 +213,7 @@ export const zh = {
|
|
|
212
213
|
settingsResetRun: '恢复默认',
|
|
213
214
|
settingsResetConfirm: '确认恢复',
|
|
214
215
|
settingsResetConfirmDetail: '确定要恢复默认设置吗?更新策略、镜像源、代理与安全限制将全部回到出厂默认值。',
|
|
215
|
-
//
|
|
216
|
-
updatesFound: '发现 {n} 个插件可更新,已加入通知中心',
|
|
216
|
+
// 更新通知(通知中心条目标题)
|
|
217
217
|
updateNoticeTitle: '发现新版本',
|
|
218
218
|
updateNoticeGo: '去更新',
|
|
219
219
|
ignoreUpdateRun: '忽略本次更新',
|
|
@@ -312,9 +312,11 @@ export const zh = {
|
|
|
312
312
|
// 「去系统诊断」直达按钮:网络不通提示下方的品牌实底按钮,跳到设置 → 系统诊断跑连通性检测
|
|
313
313
|
failNetworkRunDiag: '去系统诊断检测网络',
|
|
314
314
|
failDshMissingHint: '系统找不到 dsh 命令(dsh 未加入系统 PATH),无法调用安装器执行安装。请确认 DeepSeek Harness 已正确安装、dsh 已加入 PATH(或重新安装),然后重试。这不是插件本身的问题。',
|
|
315
|
+
failGitMissingHint: '系统找不到 git 命令(Git 未安装或未加入系统 PATH),无法从 GitHub 源安装插件。请先安装 Git(如 git-scm.com/downloads)或把 git 加入 PATH,重启 DSH 后重试。这不是插件本身的问题。',
|
|
315
316
|
failPnpmMissingHint: '安装器找到了 dsh,但系统里没有 pnpm 命令(dsh 用它来管理 profile 插件)。请先安装 pnpm(如 npm install -g pnpm),确认 pnpm 已加入 PATH 后重试。这不是插件本身的问题。',
|
|
317
|
+
failNpmMissingHint: '系统找不到 npm 命令(npm 未安装或未加入系统 PATH),无法执行全局安装。请先安装 Node.js(自带 npm),确认 npm 已加入 PATH 后重试。这不是插件本身的问题。',
|
|
316
318
|
failPnpmStoreHint: '安装器调用 pnpm 时报「store 位置不匹配」(ERR_PNPM_UNEXPECTED_STORE):你的 profile 目录里旧插件依赖是用另一个大版本的 pnpm 生成的,当前 pnpm 出于安全不认旧目录,导致任何插件装进该 profile 都会失败。请删除该 profile 目录下的 node_modules 与 pnpm-lock.yaml(可先到「系统日志」或 ~/.dsh/profiles/<profile>/hub.log 查看完整报错),再重新安装。这不是插件本身的问题。',
|
|
317
|
-
failPnpmPolicyHint: '安装器调用 pnpm
|
|
319
|
+
failPnpmPolicyHint: '安装器调用 pnpm 时被其供应链安全策略拦截——装任何插件都会撞墙,不是插件本身的问题。分两种情况处理:① 提示「Minimum release age」(ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION):包发布还不满 24 小时,pnpm 11 出于安全会拒收这种刚发布的新包。可在 pnpm 配置(pnpm-workspace.yaml)里加 minimumReleaseAge: 0 豁免,或等满 24 小时后重试。② 提示「untrusted origin」(ERR_PNPM_UNTRUSTED_ORIGIN):依赖来源不被本机 pnpm 信任,通常是该 profile 里之前用 github: 或非官方 npm 源装过插件,锁文件记录了不受信任的来源。请删除该 profile 目录下的 node_modules 与 pnpm-lock.yaml 后重新安装(可先到「系统日志」或 ~/.dsh/profiles/<profile>/hub.log 查看完整报错)。',
|
|
318
320
|
// 自定义安装三卡片入口的渠道名(报错/日志溯源:本次安装从哪个入口发起)
|
|
319
321
|
installChannelNpm: 'NPM 包',
|
|
320
322
|
installChannelGit: 'GitHub 源码',
|
|
@@ -551,6 +553,7 @@ export const en = {
|
|
|
551
553
|
diagNpmUnset: 'Unset (follow local npm config)',
|
|
552
554
|
diagGithub: 'GitHub',
|
|
553
555
|
diagCatalog: 'Catalog site',
|
|
556
|
+
diagCatalogTarget: 'Plugin marketplace',
|
|
554
557
|
diagProxy: 'HTTP proxy',
|
|
555
558
|
diagOk: 'OK',
|
|
556
559
|
diagChecking: 'Checking…',
|
|
@@ -571,8 +574,7 @@ export const en = {
|
|
|
571
574
|
settingsResetRun: 'Restore Defaults',
|
|
572
575
|
settingsResetConfirm: 'Restore Defaults',
|
|
573
576
|
settingsResetConfirmDetail: 'Reset all settings to factory defaults? Update policy, npm mirror, proxy and security restrictions will be cleared.',
|
|
574
|
-
//
|
|
575
|
-
updatesFound: '{n} plugin(s) have updates — check the notification center',
|
|
577
|
+
// Update notice (notification center item title)
|
|
576
578
|
updateNoticeTitle: 'Update available',
|
|
577
579
|
updateNoticeGo: 'Update',
|
|
578
580
|
ignoreUpdateRun: 'Ignore this update',
|
|
@@ -669,9 +671,11 @@ export const en = {
|
|
|
669
671
|
// Diagnostics shortcut button under the network-down hint: jumps to Settings → System Diagnostics
|
|
670
672
|
failNetworkRunDiag: 'Run a network diagnostic',
|
|
671
673
|
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.',
|
|
674
|
+
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.',
|
|
672
675
|
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.',
|
|
676
|
+
failNpmMissingHint: 'The npm command could not be found (npm is not installed or not on the system PATH), so a global install cannot run. Please install Node.js (which bundles npm), make sure npm is on your PATH, then retry. This is not a problem with the plugin itself.',
|
|
673
677
|
failPnpmStoreHint: 'pnpm reported an unexpected store location (ERR_PNPM_UNEXPECTED_STORE): the profile directory was previously set up with a different major version of pnpm, and the current pnpm refuses to reuse it — no plugin can be installed into that profile. Delete node_modules and pnpm-lock.yaml under the profile directory (see System Logs or ~/.dsh/profiles/<profile>/hub.log for the full output), then reinstall. This is not a problem with the plugin itself.',
|
|
674
|
-
failPnpmPolicyHint: 'The installer was blocked by pnpm supply-chain security policy
|
|
678
|
+
failPnpmPolicyHint: 'The installer was blocked by pnpm supply-chain security policy — every plugin install would hit the same wall, so this is not a problem with the plugin itself. Two cases: ① "Minimum release age" (ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION): the package was published less than 24 hours ago, and pnpm 11 refuses freshly-published packages for safety. Add minimumReleaseAge: 0 to your pnpm config (pnpm-workspace.yaml) to opt out, or wait until the 24 hours have passed and retry. ② "untrusted origin" (ERR_PNPM_UNTRUSTED_ORIGIN): a dependency source is not trusted by this machine, usually because plugins were previously installed via github: or a non-official npm registry, so the lockfile records an untrusted source. Delete node_modules and pnpm-lock.yaml under the profile directory (see System Logs or ~/.dsh/profiles/<profile>/hub.log for the full output), then reinstall.',
|
|
675
679
|
// Custom install entry channel names (the three cards) — used in error/log traceability
|
|
676
680
|
installChannelNpm: 'NPM package',
|
|
677
681
|
installChannelGit: 'GitHub source',
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { LocaleId } from '../types.ts'
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/** 插件市场官网地址。 */
|
|
12
12
|
export const SITE_URL = 'https://dsh-plugin.org/'
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/** 插件市场源码仓库:头部右上角 GitHub 图标的跳转地址 */
|
|
15
15
|
export const GITHUB_URL = 'https://github.com/dshplugin/dsh-plugin-hub'
|
|
16
16
|
|
|
17
17
|
/** Hub 自身在目录数据里的仓库标识:头部版本号旁的「可更新」徽标按它定位自己。 */
|