@noob-stupid/dsh-plugin-console 0.3.19 → 0.3.21
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.md +4 -0
- package/README.zh.md +4 -0
- package/lib/client.js +35 -6
- package/lib/index.js +150 -9
- package/package.json +39 -39
package/README.md
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
[](https://github.com/Noob-stupid/dsh-plugin-hub/commits/main)
|
|
14
14
|
[](https://github.com/Noob-stupid/dsh-plugin-hub/actions/workflows/registry.yml)
|
|
15
15
|
[](https://github.com/topics/dsh-plugin)
|
|
16
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
17
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
18
|
+
[](https://github.com/Noob-stupid/dsh-plugin-hub/releases)[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
19
|
+
[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
16
20
|
|
|
17
21
|
A **plugin management panel** for the DeepSeek Harness (DSH) Web GUI: one-click
|
|
18
22
|
enable/disable of installed plugins, a **multi-source plugin marketplace**
|
package/README.zh.md
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
[](https://github.com/Noob-stupid/dsh-plugin-hub/commits/main)
|
|
14
14
|
[](https://github.com/Noob-stupid/dsh-plugin-hub/actions/workflows/registry.yml)
|
|
15
15
|
[](https://github.com/topics/dsh-plugin)
|
|
16
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
17
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
18
|
+
[](https://github.com/Noob-stupid/dsh-plugin-hub/releases)[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
19
|
+
[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
16
20
|
|
|
17
21
|
给 DeepSeek Harness(DSH)Web 界面加上**插件管理面板**:一键启用/停用已安装插件,
|
|
18
22
|
在**多个搜索源**上浏览 dsh-plugin 插件项目(GitHub / Gitee / 自定义源),一键添加并启用——
|
package/lib/client.js
CHANGED
|
@@ -137,6 +137,7 @@ window.__ModuleLoader__.load({
|
|
|
137
137
|
githubLoggedIn: "已登录 GitHub:",
|
|
138
138
|
githubCornerOut: "未登录 GitHub",
|
|
139
139
|
hubUpdateAvailable: "下载更新",
|
|
140
|
+
cleanResiduals: "清理残余备份",
|
|
140
141
|
searchInstalledPlaceholder: "搜索已安装插件(名称或 id)",
|
|
141
142
|
extraFilter: "已装",
|
|
142
143
|
extraFilterAll: "全部",
|
|
@@ -372,6 +373,7 @@ window.__ModuleLoader__.load({
|
|
|
372
373
|
loadingDetails: "Reading details…",
|
|
373
374
|
githubLoggedIn: "GitHub signed in: ",
|
|
374
375
|
hubUpdateAvailable: "Download update",
|
|
376
|
+
cleanResiduals: "Clean residual backups",
|
|
375
377
|
githubCornerOut: "Not signed in to GitHub",
|
|
376
378
|
searchInstalledPlaceholder: "Search installed plugins (name or id)",
|
|
377
379
|
extraFilter: "Installed",
|
|
@@ -1367,7 +1369,7 @@ window.__ModuleLoader__.load({
|
|
|
1367
1369
|
// 立即本地拼官方安装命令(不等服务端后台补拉),手动安装引导即时可见
|
|
1368
1370
|
if (!data.installCommand) {
|
|
1369
1371
|
const short = repo.split("/")[1] ?? repo;
|
|
1370
|
-
data.installCommand = "git
|
|
1372
|
+
data.installCommand = "git clone --recurse-submodules https://github.com/" + repo + ".git\ncd " + short + "\npowershell -ExecutionPolicy Bypass -File install.ps1";
|
|
1371
1373
|
}
|
|
1372
1374
|
}
|
|
1373
1375
|
setRepoInfo({ status: "ready", repo, data, direct: true });
|
|
@@ -1576,7 +1578,7 @@ window.__ModuleLoader__.load({
|
|
|
1576
1578
|
() => { setSearchSourceName(""); setSearchSourceUrl(""); setSearchSourceHeaders(""); },
|
|
1577
1579
|
);
|
|
1578
1580
|
};
|
|
1579
|
-
const startJob = (repo, packageName, source = "github", kind = "plugin") => {
|
|
1581
|
+
const startJob = (repo, packageName, source = "github", kind = "plugin", update = false) => {
|
|
1580
1582
|
// 防并发(与 addLocal 一致):已有安装/更新进行中时忽略新点击(更新按钮此前可绕过,
|
|
1581
1583
|
// 反复点击会产生几十个并发任务装同一插件)
|
|
1582
1584
|
if (installing !== null) {
|
|
@@ -1587,7 +1589,7 @@ window.__ModuleLoader__.load({
|
|
|
1587
1589
|
setReloadHint(false);
|
|
1588
1590
|
setRestartHint(false);
|
|
1589
1591
|
setMessage(null);
|
|
1590
|
-
call("/plugin-console/install", { repo: repo ?? "", packageName, source, kind }).then(
|
|
1592
|
+
call("/plugin-console/install", { repo: repo ?? "", packageName, source, kind, update }).then(
|
|
1591
1593
|
(data) => {
|
|
1592
1594
|
if (data && data.jobId) {
|
|
1593
1595
|
setJobs((prev) => ({
|
|
@@ -1782,7 +1784,7 @@ window.__ModuleLoader__.load({
|
|
|
1782
1784
|
type: "button",
|
|
1783
1785
|
className: styles.toggle,
|
|
1784
1786
|
disabled: installing === entry.moduleName,
|
|
1785
|
-
onClick: () => startJob("", entry.moduleName),
|
|
1787
|
+
onClick: () => startJob("", entry.moduleName, "github", "plugin", true),
|
|
1786
1788
|
}, installing === entry.moduleName ? t("installing") : t("updateNow"))),
|
|
1787
1789
|
Array.isArray(updateCheck.depsOutdated) && updateCheck.depsOutdated.length > 0
|
|
1788
1790
|
? el("p", { className: styles.status, "data-error": "true" },
|
|
@@ -1995,7 +1997,7 @@ window.__ModuleLoader__.load({
|
|
|
1995
1997
|
type: "button",
|
|
1996
1998
|
className: styles.toggle,
|
|
1997
1999
|
disabled: installing === item.fullName,
|
|
1998
|
-
onClick: () => startJob("", installedMatch.moduleName),
|
|
2000
|
+
onClick: () => startJob("", installedMatch.moduleName, "github", "plugin", true),
|
|
1999
2001
|
}, installing === item.fullName ? t("installingLocal") : t("update") + " → v" + updateMap[item.fullName])
|
|
2000
2002
|
: el("button", {
|
|
2001
2003
|
type: "button",
|
|
@@ -2153,6 +2155,20 @@ window.__ModuleLoader__.load({
|
|
|
2153
2155
|
},
|
|
2154
2156
|
}, t("relaunchBtn"))
|
|
2155
2157
|
: null,
|
|
2158
|
+
el("button", {
|
|
2159
|
+
type: "button",
|
|
2160
|
+
style: { position: "fixed", left: 12, bottom: 12, zIndex: 1000, opacity: 0.75 },
|
|
2161
|
+
title: t("cleanResiduals"),
|
|
2162
|
+
onClick: () => {
|
|
2163
|
+
setMessage(t("cleanResiduals") + "…");
|
|
2164
|
+
call("/plugin-console/clean-residuals", {}).then(
|
|
2165
|
+
(data) => {
|
|
2166
|
+
if (data && data.ok === true) setMessage(t("cleanResiduals") + ":" + (data.count === 0 ? "无残余" : "已清理 " + data.count + " 项"));
|
|
2167
|
+
},
|
|
2168
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
2169
|
+
);
|
|
2170
|
+
},
|
|
2171
|
+
}, "🧹 " + t("cleanResiduals")),
|
|
2156
2172
|
fwShowCard
|
|
2157
2173
|
? el("div", { className: styles.detail },
|
|
2158
2174
|
el("div", { className: styles.rowTop },
|
|
@@ -2188,7 +2204,20 @@ selfUpdate !== null ? el("button", {
|
|
|
2188
2204
|
type: "button",
|
|
2189
2205
|
className: styles.ghpill,
|
|
2190
2206
|
title: t("hubUpdateAvailable") + " v" + selfUpdate.latest,
|
|
2191
|
-
onClick: () =>
|
|
2207
|
+
onClick: () => {
|
|
2208
|
+
setMessage(t("installing"));
|
|
2209
|
+
call("/plugin-console/self-update", {}).then(
|
|
2210
|
+
(data) => {
|
|
2211
|
+
if (data && data.ok === true) {
|
|
2212
|
+
setMessage((data.note ?? "已更新") + ",正在重启生效…");
|
|
2213
|
+
call("/plugin-console/restart", {}).then(() => {}, () => {});
|
|
2214
|
+
} else {
|
|
2215
|
+
setMessage(t("failed") + ":" + (data?.reason ?? "未知"));
|
|
2216
|
+
}
|
|
2217
|
+
},
|
|
2218
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2192
2221
|
}, "⬇ " + t("hubUpdateAvailable") + " v" + selfUpdate.latest) : null,
|
|
2193
2222
|
el("button", {
|
|
2194
2223
|
type: "button",
|
package/lib/index.js
CHANGED
|
@@ -35,6 +35,26 @@ const GITHUB_UA = 'dsh-plugin-console/0.1 (local dsh web instance)'
|
|
|
35
35
|
/** Gitee OAuth 端点(第三方应用需在 gitee.com → 数据管理 → 第三方应用 创建)。 */
|
|
36
36
|
const GITEE_AUTH_URL = 'https://gitee.com/oauth/authorize'
|
|
37
37
|
const GITEE_TOKEN_URL = 'https://gitee.com/oauth/token'
|
|
38
|
+
/** Gitee OAuth state 一次性凭证(防止登录 CSRF / token 替换)。 */
|
|
39
|
+
const GITEE_OAUTH_STATES = new Map()
|
|
40
|
+
function createGiteeOAuthState() {
|
|
41
|
+
const state = globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
42
|
+
const now = Date.now()
|
|
43
|
+
// 只保留最近 10 分钟内的 state
|
|
44
|
+
for (const [key, at] of GITEE_OAUTH_STATES) {
|
|
45
|
+
if (now - at > 10 * 60 * 1000) GITEE_OAUTH_STATES.delete(key)
|
|
46
|
+
}
|
|
47
|
+
GITEE_OAUTH_STATES.set(state, now)
|
|
48
|
+
return state
|
|
49
|
+
}
|
|
50
|
+
function consumeGiteeOAuthState(state) {
|
|
51
|
+
if (typeof state !== 'string' || state === '') return false
|
|
52
|
+
const at = GITEE_OAUTH_STATES.get(state)
|
|
53
|
+
if (at === undefined) return false
|
|
54
|
+
GITEE_OAUTH_STATES.delete(state)
|
|
55
|
+
return Date.now() - at <= 10 * 60 * 1000
|
|
56
|
+
}
|
|
57
|
+
|
|
38
58
|
const DEFAULT_SEARCH = 'dsh-plugin'
|
|
39
59
|
/** 技能市场收录的 topic(/search skills 分支三 topic 并行合并)。 */
|
|
40
60
|
const SKILL_TOPICS = ['agent-skills', 'claude-skills', 'dsh-skill']
|
|
@@ -1486,7 +1506,7 @@ async function pnpmInstall(profileDir, spec, registry = 'https://registry.npmmir
|
|
|
1486
1506
|
* 零依赖包可完整安装;带依赖包记录未补齐列表(不阻塞,供面板提示)。
|
|
1487
1507
|
* 返回 { version, missingDeps };失败抛错由调用方落入 next 通道。
|
|
1488
1508
|
*/
|
|
1489
|
-
async function curlManualInstall(profileDir, packageName, registries, signal = null) {
|
|
1509
|
+
async function curlManualInstall(profileDir, packageName, registries, signal = null, exactVersion = null) {
|
|
1490
1510
|
let meta = null
|
|
1491
1511
|
let metaError = null
|
|
1492
1512
|
for (const reg of registries) {
|
|
@@ -1501,7 +1521,7 @@ async function curlManualInstall(profileDir, packageName, registries, signal = n
|
|
|
1501
1521
|
}
|
|
1502
1522
|
}
|
|
1503
1523
|
if (!meta || typeof meta !== 'object') throw new Error(`curl 通道:无法获取 registry 元数据(${metaError?.message ?? '未知'})`)
|
|
1504
|
-
const version = meta['dist-tags']?.latest ?? null
|
|
1524
|
+
const version = exactVersion ?? meta['dist-tags']?.latest ?? null
|
|
1505
1525
|
const tarball = version ? meta.versions?.[version]?.dist?.tarball ?? null : null
|
|
1506
1526
|
if (!version || !tarball) throw new Error('curl 通道:registry 无 dist-tags.latest / tarball')
|
|
1507
1527
|
const bin = process.platform === 'win32' ? 'curl.exe' : 'curl'
|
|
@@ -1511,7 +1531,14 @@ async function curlManualInstall(profileDir, packageName, registries, signal = n
|
|
|
1511
1531
|
const tgz = join(tmp, 'pkg.tgz')
|
|
1512
1532
|
await execFileAsync(bin, ['-s', '-L', '-m', '60', '-o', tgz, tarball], { timeout: 70000, windowsHide: true, ...(signal ? { signal } : {}) })
|
|
1513
1533
|
await execFileAsync('tar', ['-xzf', tgz, '-C', tmp], { timeout: 30000, windowsHide: true, ...(signal ? { signal } : {}) })
|
|
1514
|
-
|
|
1534
|
+
let pkgPath = join(tmp, 'package')
|
|
1535
|
+
if (!existsSync(join(pkgPath, 'package.json'))) {
|
|
1536
|
+
const candidates = readdirSync(tmp, { withFileTypes: true })
|
|
1537
|
+
.filter((d) => d.isDirectory())
|
|
1538
|
+
.map((d) => join(tmp, d.name))
|
|
1539
|
+
pkgPath = candidates.find((p) => existsSync(join(p, 'package.json'))) ?? pkgPath
|
|
1540
|
+
}
|
|
1541
|
+
if (!existsSync(join(pkgPath, 'package.json'))) throw new Error('curl 通道:解压后未找到含 package.json 的目录')
|
|
1515
1542
|
// 盒子实验:解压后先验证,通过才覆盖正式位置(失败保留旧版本,服务不中断)
|
|
1516
1543
|
const box = verifyPackageBox(pkgPath, profileDir, packageName)
|
|
1517
1544
|
const pkg = JSON.parse(readFileSync(join(pkgPath, 'package.json'), 'utf8'))
|
|
@@ -1598,7 +1625,7 @@ function verifyPackageBox(pkgPath, profileDir, packageName, refCheckRoot = null)
|
|
|
1598
1625
|
const refs = parseBundlePatchRefs(readFileSync(bundlePatch, 'utf8'))
|
|
1599
1626
|
const missingRefs = refs.filter((r) => !existsSync(join(checkRoot, r.name)))
|
|
1600
1627
|
if (missingRefs.length > 0) {
|
|
1601
|
-
|
|
1628
|
+
note.push(`bundle 引用缺失:${missingRefs.map((r) => r.name).join('、')}(安装后由聚合完整性检查补齐/禁用)`)
|
|
1602
1629
|
}
|
|
1603
1630
|
if (refs.length > 0) note.push(`bundle 引用 ${refs.length} 个已就位`)
|
|
1604
1631
|
}
|
|
@@ -2386,6 +2413,36 @@ async function aiRepair(job, ctx, profileDir, candidates, lastError) {
|
|
|
2386
2413
|
}
|
|
2387
2414
|
}
|
|
2388
2415
|
|
|
2416
|
+
/** 聚合包更新后的子包版本对齐:按新版聚合包声明的依赖版本,逐个对齐已存在的子包。 */
|
|
2417
|
+
async function syncAggregateSubpackageVersions(profileDir, packageName, registries) {
|
|
2418
|
+
const pkgPath = join(profileDir, 'node_modules', packageName, 'package.json')
|
|
2419
|
+
if (!existsSync(pkgPath)) return []
|
|
2420
|
+
let pkg = null
|
|
2421
|
+
try { pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) } catch { return [] }
|
|
2422
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.peerDependencies ?? {}) }
|
|
2423
|
+
const updated = []
|
|
2424
|
+
for (const dep of Object.keys(deps)) {
|
|
2425
|
+
if (dep.startsWith('@deepseek-ai/')) continue
|
|
2426
|
+
const spec = String(deps[dep] ?? '').replace(/^[\^~>=< ]+/u, '')
|
|
2427
|
+
if (!spec) continue
|
|
2428
|
+
const currentPath = join(profileDir, 'node_modules', dep, 'package.json')
|
|
2429
|
+
if (!existsSync(currentPath)) {
|
|
2430
|
+
try {
|
|
2431
|
+
await curlManualInstall(profileDir, dep, registries, null, spec)
|
|
2432
|
+
updated.push(`${dep}@${spec}(新装)`)
|
|
2433
|
+
} catch {}
|
|
2434
|
+
continue
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
const current = JSON.parse(readFileSync(currentPath, 'utf8'))
|
|
2438
|
+
if (current.version === spec) continue
|
|
2439
|
+
await curlManualInstall(profileDir, dep, registries, null, spec)
|
|
2440
|
+
updated.push(`${dep}@${spec}`)
|
|
2441
|
+
} catch {}
|
|
2442
|
+
}
|
|
2443
|
+
return updated
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2389
2446
|
async function runInstallJob(job, ctx) {
|
|
2390
2447
|
try {
|
|
2391
2448
|
job.stage = 'preparing'
|
|
@@ -2465,7 +2522,7 @@ async function runInstallJob(job, ctx) {
|
|
|
2465
2522
|
if (existsSync(existingTarget)) {
|
|
2466
2523
|
try {
|
|
2467
2524
|
const existingPkg = JSON.parse(readFileSync(existingTarget, 'utf8'))
|
|
2468
|
-
if (existingPkg && existingPkg.name === name) {
|
|
2525
|
+
if (existingPkg && existingPkg.name === name && job.update !== true) {
|
|
2469
2526
|
installedName = name
|
|
2470
2527
|
job.curlNote = `已检测到本地已安装 ${name}@${existingPkg.version ?? '?'},跳过重复下载`
|
|
2471
2528
|
}
|
|
@@ -2615,6 +2672,13 @@ async function runInstallJob(job, ctx) {
|
|
|
2615
2672
|
return
|
|
2616
2673
|
}
|
|
2617
2674
|
job.packageName = installedName
|
|
2675
|
+
// 聚合包子包版本对齐(确保更新后所有子包也到新版声明版本)
|
|
2676
|
+
try {
|
|
2677
|
+
const synced = await syncAggregateSubpackageVersions(profileDir, installedName, registries)
|
|
2678
|
+
if (synced.length > 0) {
|
|
2679
|
+
job.bundleNote = (job.bundleNote ? job.bundleNote + ';' : '') + `子包版本对齐:更新 ${synced.length} 个(${synced.join('、')})`
|
|
2680
|
+
}
|
|
2681
|
+
} catch {}
|
|
2618
2682
|
// 聚合包完整性保障(防崩):bundle patch 引用的包缺失 → 补装;仍缺自动禁用该行
|
|
2619
2683
|
try {
|
|
2620
2684
|
const integrity = await ensureBundlePatchIntegrity(profileDir, installedName, patchPath)
|
|
@@ -2757,13 +2821,15 @@ async function handle(ctx, req, res) {
|
|
|
2757
2821
|
const redirect = `http://127.0.0.1:${port}/plugin-console/gitee-oauth-callback`
|
|
2758
2822
|
// scope 请求 user_info + projects:Gitee 会校验请求的 scope 必须在应用已勾选的权限范围内,
|
|
2759
2823
|
// 应用权限必须同步勾选 user_info、projects,否则报「请求范围无效、未知或格式不正确」
|
|
2760
|
-
const
|
|
2761
|
-
|
|
2824
|
+
const state = createGiteeOAuthState()
|
|
2825
|
+
const url = `${GITEE_AUTH_URL}?client_id=${encodeURIComponent(gitee.clientId)}&redirect_uri=${encodeURIComponent(redirect)}&response_type=code&scope=${encodeURIComponent('user_info projects')}&state=${encodeURIComponent(state)}`
|
|
2826
|
+
sendJson(res, 200, { ok: true, url, redirect, state })
|
|
2762
2827
|
return
|
|
2763
2828
|
}
|
|
2764
2829
|
|
|
2765
2830
|
if (method === 'GET' && pathname === `${ROUTE_PREFIX}/gitee-oauth-callback`) {
|
|
2766
2831
|
const code = new URL(req.url ?? '/', 'http://x').searchParams.get('code')
|
|
2832
|
+
const state = new URL(req.url ?? '/', 'http://x').searchParams.get('state')
|
|
2767
2833
|
const sources = readSources()
|
|
2768
2834
|
const gitee = readGiteeConfig(sources)
|
|
2769
2835
|
const failPage = (text) => {
|
|
@@ -2774,6 +2840,10 @@ async function handle(ctx, req, res) {
|
|
|
2774
2840
|
failPage('这是 Gitee 授权回调地址,不能直接访问。<br>正确流程:插件面板 → 软件源管理 → 填入 client_id / client_secret → 保存配置 → 点击「授权登录 Gitee」,授权完成后会自动跳回这里。')
|
|
2775
2841
|
return
|
|
2776
2842
|
}
|
|
2843
|
+
if (!state || !consumeGiteeOAuthState(state)) {
|
|
2844
|
+
failPage('Gitee OAuth state 校验失败,请重新发起授权。')
|
|
2845
|
+
return
|
|
2846
|
+
}
|
|
2777
2847
|
if (!gitee.clientId || !gitee.clientSecret) {
|
|
2778
2848
|
failPage('未配置 Gitee 应用:请先在 gitee.com 创建第三方应用(回调地址填本页完整地址),再在插件面板 → 软件源管理 中填入 client_id / client_secret 并保存。')
|
|
2779
2849
|
return
|
|
@@ -3179,10 +3249,10 @@ async function handle(ctx, req, res) {
|
|
|
3179
3249
|
const short = repo.split('/')[1] ?? repo
|
|
3180
3250
|
const hasInstallScript = (await rawTextWithFallback(repo, branch, 'install.ps1')) !== null
|
|
3181
3251
|
|| (await rawTextWithFallback(repo, branch, 'install.sh')) !== null
|
|
3182
|
-
// 纯命令(无注释,CMD/PowerShell
|
|
3252
|
+
// 纯命令(无注释,CMD/PowerShell 通用);不再关闭 TLS 校验;
|
|
3183
3253
|
// 脚本用 powershell -File 调用,CMD 里也能跑
|
|
3184
3254
|
installCommand = [
|
|
3185
|
-
`git
|
|
3255
|
+
`git clone --recurse-submodules https://github.com/${repo}.git`,
|
|
3186
3256
|
`cd ${short}`,
|
|
3187
3257
|
hasInstallScript
|
|
3188
3258
|
? `powershell -ExecutionPolicy Bypass -File install.ps1`
|
|
@@ -3912,6 +3982,76 @@ async function handle(ctx, req, res) {
|
|
|
3912
3982
|
return
|
|
3913
3983
|
}
|
|
3914
3984
|
|
|
3985
|
+
if (pathname === `${ROUTE_PREFIX}/clean-residuals`) {
|
|
3986
|
+
// 清理残余备份/旧子包:删除 .old-* 备份目录,以及聚合包未声明的 @linxin666 旧子包
|
|
3987
|
+
const profileDir = dirname(findPatchPath(ctx))
|
|
3988
|
+
const nodeModules = join(profileDir, 'node_modules')
|
|
3989
|
+
const removed = []
|
|
3990
|
+
// 1) 删除 .old-* 残余备份(顶层 + 作用域目录)
|
|
3991
|
+
const scanDirs = [nodeModules]
|
|
3992
|
+
try { if (existsSync(join(nodeModules, '@linxin666'))) scanDirs.push(join(nodeModules, '@linxin666')) } catch {}
|
|
3993
|
+
for (const dir of scanDirs) {
|
|
3994
|
+
try {
|
|
3995
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3996
|
+
if (!entry.isDirectory()) continue
|
|
3997
|
+
if (!/\.old-/u.test(entry.name)) continue
|
|
3998
|
+
const target = join(dir, entry.name)
|
|
3999
|
+
rmSync(target, { recursive: true, force: true })
|
|
4000
|
+
removed.push(entry.name)
|
|
4001
|
+
}
|
|
4002
|
+
} catch {}
|
|
4003
|
+
}
|
|
4004
|
+
// 2) 删除聚合包未声明的 @linxin666 旧子包(以 dsh-web-ui-all 的 dependencies 为准)
|
|
4005
|
+
try {
|
|
4006
|
+
const allPkgPath = join(nodeModules, '@linxin666', 'dsh-web-ui-all', 'package.json')
|
|
4007
|
+
if (existsSync(allPkgPath)) {
|
|
4008
|
+
const allPkg = JSON.parse(readFileSync(allPkgPath, 'utf8'))
|
|
4009
|
+
const declared = new Set(Object.keys(allPkg.dependencies ?? {}).filter((n) => n.startsWith('@linxin666/')))
|
|
4010
|
+
declared.add('@linxin666/dsh-web-ui-all')
|
|
4011
|
+
const scoped = join(nodeModules, '@linxin666')
|
|
4012
|
+
for (const entry of readdirSync(scoped, { withFileTypes: true })) {
|
|
4013
|
+
if (!entry.isDirectory()) continue
|
|
4014
|
+
const full = '@linxin666/' + entry.name
|
|
4015
|
+
if (declared.has(full)) continue
|
|
4016
|
+
const pkgJson = join(scoped, entry.name, 'package.json')
|
|
4017
|
+
if (existsSync(pkgJson)) {
|
|
4018
|
+
rmSync(join(scoped, entry.name), { recursive: true, force: true })
|
|
4019
|
+
removed.push(full)
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
} catch {}
|
|
4024
|
+
sendJson(res, 200, { ok: true, removed, count: removed.length })
|
|
4025
|
+
return
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
if (pathname === `${ROUTE_PREFIX}/self-update`) {
|
|
4029
|
+
// Hub 自身一键更新:下载 npm 最新 tarball 到当前 profile,成功后由前端重启生效
|
|
4030
|
+
let selfVersion = null
|
|
4031
|
+
try {
|
|
4032
|
+
const selfPkg = JSON.parse(readFileSync(createRequire(import.meta.url).resolve('../package.json'), 'utf8'))
|
|
4033
|
+
selfVersion = typeof selfPkg.version === 'string' ? selfPkg.version : null
|
|
4034
|
+
} catch {}
|
|
4035
|
+
let latest = null
|
|
4036
|
+
try {
|
|
4037
|
+
const data = await fetchJsonUrl('https://registry.npmmirror.com/@noob-stupid%2fdsh-plugin-console')
|
|
4038
|
+
latest = data?.['dist-tags']?.latest ?? null
|
|
4039
|
+
} catch {}
|
|
4040
|
+
if (!latest || selfVersion === null || latest === selfVersion) {
|
|
4041
|
+
sendJson(res, 200, { ok: false, current: selfVersion, latest, updated: false, reason: latest === selfVersion ? '已是最新版本' : '版本检测失败' })
|
|
4042
|
+
return
|
|
4043
|
+
}
|
|
4044
|
+
const profileDir = dirname(findPatchPath(ctx))
|
|
4045
|
+
const registries = orderedRegistries(readSources())
|
|
4046
|
+
try {
|
|
4047
|
+
const info = await curlManualInstall(profileDir, '@noob-stupid/dsh-plugin-console', registries)
|
|
4048
|
+
sendJson(res, 200, { ok: true, current: selfVersion, latest, updated: true, version: info.version, note: '已下载最新代码,重启服务后生效' })
|
|
4049
|
+
} catch (error) {
|
|
4050
|
+
sendError(res, 500, `Hub 自动更新失败:${error instanceof Error ? error.message : String(error)}`)
|
|
4051
|
+
}
|
|
4052
|
+
return
|
|
4053
|
+
}
|
|
4054
|
+
|
|
3915
4055
|
if (pathname === `${ROUTE_PREFIX}/install`) {
|
|
3916
4056
|
// repo 允许为空:纯 registry 更新(已安装插件的"检测更新"走此路径,无 git 兜底)
|
|
3917
4057
|
const rawRepo = typeof body.repo === 'string' ? body.repo.trim() : ''
|
|
@@ -3963,6 +4103,7 @@ async function handle(ctx, req, res) {
|
|
|
3963
4103
|
aiNote: null,
|
|
3964
4104
|
subpackages: null,
|
|
3965
4105
|
lastError: null,
|
|
4106
|
+
update: body.update === true,
|
|
3966
4107
|
kind,
|
|
3967
4108
|
}
|
|
3968
4109
|
installJobs.set(job.id, job)
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "DSH plugin management panel & marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "https://github.com/Noob-stupid/dsh-plugin-hub"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"main": "./lib/index.js",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": "./lib/index.js",
|
|
13
|
-
"./client": "./lib/client.js",
|
|
14
|
-
"./package.json": "./package.json"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"dsh-plugin",
|
|
18
|
-
"deepseek-harness",
|
|
19
|
-
"plugin-manager"
|
|
20
|
-
],
|
|
21
|
-
"dsh": {
|
|
22
|
-
"bundle": {
|
|
23
|
-
"patch": "./cordis.patch.yml"
|
|
24
|
-
},
|
|
25
|
-
"client": {
|
|
26
|
-
"platform": "web",
|
|
27
|
-
"inject": [
|
|
28
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
29
|
-
"@deepseek-ai/dsh-client-locale",
|
|
30
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"lib",
|
|
36
|
-
"cordis.patch.yml"
|
|
37
|
-
],
|
|
38
|
-
"license": "MIT"
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
+
"version": "0.3.21",
|
|
4
|
+
"description": "DSH plugin management panel & marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/Noob-stupid/dsh-plugin-hub"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./lib/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./lib/index.js",
|
|
13
|
+
"./client": "./lib/client.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dsh-plugin",
|
|
18
|
+
"deepseek-harness",
|
|
19
|
+
"plugin-manager"
|
|
20
|
+
],
|
|
21
|
+
"dsh": {
|
|
22
|
+
"bundle": {
|
|
23
|
+
"patch": "./cordis.patch.yml"
|
|
24
|
+
},
|
|
25
|
+
"client": {
|
|
26
|
+
"platform": "web",
|
|
27
|
+
"inject": [
|
|
28
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
29
|
+
"@deepseek-ai/dsh-client-locale",
|
|
30
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"lib",
|
|
36
|
+
"cordis.patch.yml"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
}
|