@noob-stupid/dsh-plugin-console 0.3.56 → 0.3.58
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 +2 -2
- package/README.zh.md +2 -2
- package/lib/client.js +1 -1
- package/lib/index.js +101 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -308,7 +308,7 @@ MIT
|
|
|
308
308
|
|
|
309
309
|
The layered-refactor preview lives in its **own repository**: [**`Noob-stupid/dsh-plugin-hub-refactor`**](https://github.com/Noob-stupid/dsh-plugin-hub-refactor)
|
|
310
310
|
|
|
311
|
-
- **What it is**: the monolithic `lib/index.js` (8547 lines) split into layers — `lib/index.js` **141 lines** + `lib/server/**` **38 modules**; **feature-equivalent to `0.3.
|
|
311
|
+
- **What it is**: the monolithic `lib/index.js` (8547 lines) split into layers — `lib/index.js` **141 lines** + `lib/server/**` **38 modules**; **feature-equivalent to `0.3.57`**, maintainability only;
|
|
312
312
|
- **Verified**: 19 test suites green · 8 architecture-guard assertions · **route inventory identical** (`status` + response fields) against the stable build;
|
|
313
313
|
- **Not yet exercised**: real framework upgrade / rollback, restart guardian, component start/stop, real AI run, Gitee OAuth (~10–25% long-tail risk, subjective);
|
|
314
|
-
- **For everyday use** stick to npm `@noob-stupid/dsh-plugin-console@0.3.
|
|
314
|
+
- **For everyday use** stick to npm `@noob-stupid/dsh-plugin-console@0.3.58`, or `main` of this repo.
|
package/README.zh.md
CHANGED
|
@@ -275,9 +275,9 @@ MIT
|
|
|
275
275
|
|
|
276
276
|
分层重构预览线在**独立仓库**:[**`Noob-stupid/dsh-plugin-hub-refactor`**](https://github.com/Noob-stupid/dsh-plugin-hub-refactor)
|
|
277
277
|
|
|
278
|
-
- **它是什么**:把单体 `lib/index.js`(8547 行)拆成分层结构 —— `lib/index.js` **141 行** + `lib/server/**` **38 个模块**,**功能与 `0.3.
|
|
278
|
+
- **它是什么**:把单体 `lib/index.js`(8547 行)拆成分层结构 —— `lib/index.js` **141 行** + `lib/server/**` **38 个模块**,**功能与 `0.3.57` 等价**,只为可维护性;
|
|
279
279
|
- **已验证**:19 套测试全绿 · 8 条架构守卫断言 · 与稳定版逐条对打**路由清单一致**(`status` + 响应字段);
|
|
280
280
|
- **已实测**(2026-09-20 真装真卸演练):普通插件 / bundle 插件 / 无 npm 仓库 / 套装 / 技能 / 聚合仓库子包 / 仓库落地 / 服务器组件启停;
|
|
281
281
|
- **尚未实测**:真框架升级 / 真回滚、重启守护链路、AI 真跑、Gitee OAuth 回调(长尾风险主观估计 **10%~25%**);
|
|
282
|
-
- **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console@0.3.
|
|
282
|
+
- **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console@0.3.58`,或本仓库 `main`。
|
|
283
283
|
- 请多反馈问题
|
package/lib/client.js
CHANGED
|
@@ -2519,7 +2519,7 @@ window.__ModuleLoader__.load({
|
|
|
2519
2519
|
} else if (data.ai === true) {
|
|
2520
2520
|
setMessage(t("installed") + ":" + data.packageName + "。" + (data.aiNote ?? t("aiRepaired")));
|
|
2521
2521
|
} else {
|
|
2522
|
-
setMessage(t("installed") + ":" + data.packageName + "(" + (data.entryId ?? "") + ")。" + (data.curlNote ?? (data.bundle ? t("bundleNote") : t("installNote"))) + (data.bundleNote ? "。" + data.bundleNote : ""));
|
|
2522
|
+
setMessage(t("installed") + ":" + data.packageName + "(" + (data.entryId ?? "") + ")。" + (data.curlNote ?? (data.bundle ? t("bundleNote") : t("installNote"))) + (data.bundleNote ? "。" + data.bundleNote : "") + (data.lockNote ? "。⚠️ " + data.lockNote : ""));
|
|
2523
2523
|
}
|
|
2524
2524
|
if (data.kind === "skill") {
|
|
2525
2525
|
// 技能无需页面刷新
|
package/lib/index.js
CHANGED
|
@@ -2692,7 +2692,7 @@ export async function searchSubpackageItems(query, token = null, signal = null)
|
|
|
2692
2692
|
)
|
|
2693
2693
|
for (const hit of (codeData.items ?? []).slice(0, 10)) {
|
|
2694
2694
|
const hitPath = typeof hit.path === 'string' ? hit.path : ''
|
|
2695
|
-
if (!/^(
|
|
2695
|
+
if (!/^(?!node_modules\/)[^/]+(?:\/[^/]+)?\/package\.json$/u.test(hitPath)) continue
|
|
2696
2696
|
const repoName = hit.repository?.full_name ?? ''
|
|
2697
2697
|
if (!repoName) continue
|
|
2698
2698
|
const dir = hitPath.split('/').slice(0, -1).join('/')
|
|
@@ -3211,7 +3211,10 @@ function installJobView(job) {
|
|
|
3211
3211
|
subpackages: job.subpackages ?? null,
|
|
3212
3212
|
source: job.source ?? 'github',
|
|
3213
3213
|
curlNote: job.curlNote ?? null,
|
|
3214
|
-
bundleNote: job.bundleNote ?? null,
|
|
3214
|
+
bundleNote: job.bundleNote ?? null,
|
|
3215
|
+
lockUpdated: job.lockUpdated ?? null,
|
|
3216
|
+
lockVersion: job.lockVersion ?? null,
|
|
3217
|
+
lockNote: job.lockNote ?? null,
|
|
3215
3218
|
compatNote: job.compatNote ?? null,
|
|
3216
3219
|
kind: job.kind ?? 'plugin',
|
|
3217
3220
|
skillName: job.skillName ?? null,
|
|
@@ -3636,7 +3639,10 @@ export async function runSuiteInstallJob(job, ctx) {
|
|
|
3636
3639
|
copyTree(subDir, target)
|
|
3637
3640
|
const taken = new Set(listEntries(ctx).map((e) => e.rowId))
|
|
3638
3641
|
const entryId = deriveEntryId(pkg.name, taken)
|
|
3639
|
-
await appendInsert(patchPath, entryId, pkg.name)
|
|
3642
|
+
await appendInsert(patchPath, entryId, pkg.name)
|
|
3643
|
+
// 记下套装装配出的包名:它们是 copyTree 铺进去的、不在 pnpm-lock.yaml 里,交给调用方统一对账
|
|
3644
|
+
if (!Array.isArray(job.suiteInstalled)) job.suiteInstalled = []
|
|
3645
|
+
if (!job.suiteInstalled.includes(pkg.name)) job.suiteInstalled.push(pkg.name)
|
|
3640
3646
|
report.push({ component: sub.name, type: 'plugin', ok: true, note: `已安装 ${pkg.name}(HMR 生效)` })
|
|
3641
3647
|
handled = true
|
|
3642
3648
|
}
|
|
@@ -3817,7 +3823,7 @@ async function fetchSubpackageNames(repo, branch, auth) {
|
|
|
3817
3823
|
try {
|
|
3818
3824
|
const data = await githubJson(`${GITHUB_API}/repos/${repo}/git/trees/${encodeURIComponent(branch)}?recursive=1`, undefined, auth)
|
|
3819
3825
|
const paths = (data.tree ?? [])
|
|
3820
|
-
.filter((node) => node.type === 'blob' && /^(
|
|
3826
|
+
.filter((node) => node.type === 'blob' && /^(?!node_modules\/)[^/]+(?:\/[^/]+)?\/package\.json$/u.test(node.path))
|
|
3821
3827
|
.map((node) => node.path)
|
|
3822
3828
|
// 并行读取:黑洞期单条最坏 40s,24 条串行会拖到十几分钟
|
|
3823
3829
|
const results = await Promise.all(paths.slice(0, 24).map(async (path) => {
|
|
@@ -5199,6 +5205,17 @@ async function runInstallJob(job, ctx) {
|
|
|
5199
5205
|
if (resolveInstallKind(job.kind, await probeGitmodules(job.repo)) === 'suite') {
|
|
5200
5206
|
job.kind = 'suite'
|
|
5201
5207
|
const suiteResult = await runSuiteInstallJob(job, ctx)
|
|
5208
|
+
// 套装装配出的普通插件同样对账,避免之后被 pnpm 还原/清理
|
|
5209
|
+
if (Array.isArray(job.suiteInstalled) && job.suiteInstalled.length > 0) {
|
|
5210
|
+
try {
|
|
5211
|
+
const lock = await reconcileLockfile({ profileDir, packages: job.suiteInstalled.map((name) => ({ name })), registries })
|
|
5212
|
+
job.lockUpdated = lock.lockUpdated
|
|
5213
|
+
job.lockVersion = lock.lockVersion
|
|
5214
|
+
job.lockMethod = lock.method
|
|
5215
|
+
job.lockPackages = lock.packages
|
|
5216
|
+
if (lock.lockNote !== null) job.lockNote = lock.lockNote
|
|
5217
|
+
} catch {}
|
|
5218
|
+
}
|
|
5202
5219
|
// clone 后才发现没有 .gitmodules(探测假阳性 / 仓库已重构)→ 回落普通安装,不给用户一个失败
|
|
5203
5220
|
if (suiteResult?.notASuite !== true) return
|
|
5204
5221
|
job.kind = 'plugin'
|
|
@@ -5294,6 +5311,11 @@ async function runInstallJob(job, ctx) {
|
|
|
5294
5311
|
job.candidateTotal = candidates.length
|
|
5295
5312
|
job.candidateDone = false
|
|
5296
5313
|
for (let index = 0; index < candidates.length && installedName === null; index += 1) {
|
|
5314
|
+
// Issue(2026-09-21):subpackageMode 只表达"优先装子包",不该连坐禁用其它通道。
|
|
5315
|
+
// curl 通道按 name 走 registry(与根包是否 private 无关)→ 子包候选也开放;
|
|
5316
|
+
// release/git 通道是按 job.repo 说的 → 只在"候选就是被请求的那个包"时试,避免对 private 根做无意义尝试。
|
|
5317
|
+
const repoChannelAllowed = !subpackageMode || job.packageName === null || name === job.packageName
|
|
5318
|
+
// 明明 UI 推荐的 `dsh plugin add github:owner/repo` 和 release 通道都没被尝试过。现在:
|
|
5297
5319
|
if (Date.now() > deadline) break
|
|
5298
5320
|
const name = candidates[index]
|
|
5299
5321
|
job.candidateIndex = index + 1
|
|
@@ -5312,7 +5334,7 @@ async function runInstallJob(job, ctx) {
|
|
|
5312
5334
|
}
|
|
5313
5335
|
}
|
|
5314
5336
|
// 加法并行竞速:pnpm 与 curl 同时启动,先成功者生效;失败方 abort,不影响后续串行通道
|
|
5315
|
-
if (installedName === null && !
|
|
5337
|
+
if (installedName === null && !expanded) {
|
|
5316
5338
|
const raced = await raceInstallChannels(profileDir, name, registries)
|
|
5317
5339
|
if (raced) {
|
|
5318
5340
|
installedName = name
|
|
@@ -5337,7 +5359,7 @@ async function runInstallJob(job, ctx) {
|
|
|
5337
5359
|
if (installedName === null) {
|
|
5338
5360
|
// 通道 n+1:curl 手动安装(node 网络黑洞时 pnpm 下载卡死、curl 可用)——
|
|
5339
5361
|
// 下载 registry tarball 解压到 node_modules,零依赖包可完整安装
|
|
5340
|
-
if (!
|
|
5362
|
+
if (!expanded) {
|
|
5341
5363
|
try {
|
|
5342
5364
|
const info = await curlManualInstall(profileDir, name, registries)
|
|
5343
5365
|
installedName = name
|
|
@@ -5353,7 +5375,7 @@ async function runInstallJob(job, ctx) {
|
|
|
5353
5375
|
// 通道 n+1b:GitHub release 下载安装(npm 上不存在的包,例如面板自身
|
|
5354
5376
|
// @deepseek-ai/dsh-plugin-console)——拉 latest release 源码 tarball → 盒子验证 → 覆盖。
|
|
5355
5377
|
// 触发条件:curl 通道失败(registry 404/网络),且能从 job.repo 或已装包 repository 反查到 GitHub。
|
|
5356
|
-
if (
|
|
5378
|
+
if (repoChannelAllowed && !expanded) {
|
|
5357
5379
|
let ghRepo = typeof job.repo === 'string' && job.repo !== '' && job.repo.includes('/') ? job.repo : null
|
|
5358
5380
|
if (ghRepo === null) {
|
|
5359
5381
|
try {
|
|
@@ -5378,7 +5400,7 @@ async function runInstallJob(job, ctx) {
|
|
|
5378
5400
|
}
|
|
5379
5401
|
if (installedName === null) {
|
|
5380
5402
|
// 通道 n+2:git 通道(GitHub 走加速代理+直连;Gitee 走对应平台;各 60 秒封顶)
|
|
5381
|
-
if (
|
|
5403
|
+
if (repoChannelAllowed && !expanded) {
|
|
5382
5404
|
const gitSpecs = job.source === 'gitee'
|
|
5383
5405
|
? [`git+https://gitee.com/${job.repo}.git`]
|
|
5384
5406
|
: [
|
|
@@ -5492,6 +5514,20 @@ async function runInstallJob(job, ctx) {
|
|
|
5492
5514
|
const adapt = await maybeAutoAdaptCompat({ profileDir, packageName: installedName, syncedNames, patchPath, ctx })
|
|
5493
5515
|
if (adapt !== null && adapt.ran === true) job.compatNote = adapt.note
|
|
5494
5516
|
} catch {}
|
|
5517
|
+
// lock 对账(2026-09-21,用户报告的自更新缺陷同源):主包 + 本次被补装/对齐的聚合子包一起核对,
|
|
5518
|
+
// 一次 pnpm add 把漂移的包全写进 lock;对不上就留 lockNote,让面板如实告知(不假装成功)。
|
|
5519
|
+
try {
|
|
5520
|
+
const lock = await reconcileLockfile({
|
|
5521
|
+
profileDir,
|
|
5522
|
+
packages: [installedName, ...syncedNames].map((name) => ({ name })),
|
|
5523
|
+
registries,
|
|
5524
|
+
})
|
|
5525
|
+
job.lockUpdated = lock.lockUpdated
|
|
5526
|
+
job.lockVersion = lock.lockVersion
|
|
5527
|
+
job.lockMethod = lock.method
|
|
5528
|
+
job.lockPackages = lock.packages
|
|
5529
|
+
if (lock.lockNote !== null) job.lockNote = lock.lockNote
|
|
5530
|
+
} catch {}
|
|
5495
5531
|
job.stage = 'configuring'
|
|
5496
5532
|
// 2026-09-06 事故(i18n 更新变重复行):@linxin666/dsh-i18n 声明了 dsh.bundle.patch,
|
|
5497
5533
|
// 更新流程按「bundle 安装规则」把它追加进 bundles → 与全家桶内的 web-ui-i18n 行(同包)重复。
|
|
@@ -5694,6 +5730,63 @@ export async function selfUpdateToLatest({ profileDir, latest, registries, curlM
|
|
|
5694
5730
|
return { method, spec, installedVersion: installed, lockVersion: lock, lockUpdated, lockNote, command, note: noteParts.join(';'), errors }
|
|
5695
5731
|
}
|
|
5696
5732
|
|
|
5733
|
+
|
|
5734
|
+
/**
|
|
5735
|
+
* 通用 lock 对账(**任何**安装通道装完都该调用,支持一次对账多个包)。
|
|
5736
|
+
* 走非 pnpm 通道(并行 curl / curl tarball / GitHub Release / git 装配 / 套装 copyTree / 聚合子包补装)装的包,
|
|
5737
|
+
* node_modules 里的版本与 `pnpm-lock.yaml` 记的版本可能不一致——之后任何 pnpm 操作(开关插件改
|
|
5738
|
+
* `dsh.profile.bundles`、`dsh plugin add/remove`)都可能按 lock 把它还原或当外来物处理。这里:
|
|
5739
|
+
* ① 全部一致 → 直接返回(不跑 pnpm,零成本);
|
|
5740
|
+
* ② 有漂移 → **一次** `pnpm add <pkg1>@<v1> <pkg2>@<v2> …` 把漂移包精确写进 lock;
|
|
5741
|
+
* ③ 仍对不上 → 返回 lockNote(逐包列出)+ 可复制命令,由调用方如实展示,绝不假装成功。
|
|
5742
|
+
*/
|
|
5743
|
+
export async function reconcileLockfile({ profileDir, packageName = null, packages = null, registries = [], pnpmAdd = pnpmInstall, execOpts = {} }) {
|
|
5744
|
+
const targets = Array.isArray(packages)
|
|
5745
|
+
? packages.filter((p) => p && typeof p.name === 'string' && p.name !== '')
|
|
5746
|
+
: (packageName === null ? [] : [{ name: packageName, version: null }])
|
|
5747
|
+
const unique = []
|
|
5748
|
+
for (const t of targets) if (!unique.some((u) => u.name === t.name)) unique.push(t)
|
|
5749
|
+
const command = (name, version) => `dsh plugin --profile <你的profile> add ${name}@${version ?? '<版本>'}`
|
|
5750
|
+
const snap = () => unique.map((t) => ({
|
|
5751
|
+
name: t.name,
|
|
5752
|
+
spec: profileSpecOf(profileDir, t.name),
|
|
5753
|
+
installed: readInstalledVersion(profileDir, t.name),
|
|
5754
|
+
lock: lockVersionOf(profileDir, t.name),
|
|
5755
|
+
}))
|
|
5756
|
+
const drift = (rows) => rows.filter((r) => r.installed === null || r.installed !== r.lock)
|
|
5757
|
+
let rows = snap()
|
|
5758
|
+
let drifted = drift(rows)
|
|
5759
|
+
const errors = []
|
|
5760
|
+
let method = null
|
|
5761
|
+
if (drifted.length > 0) {
|
|
5762
|
+
const specs = drifted.filter((r) => r.installed !== null).map((r) => `${r.name}@${r.installed}`)
|
|
5763
|
+
if (specs.length > 0) {
|
|
5764
|
+
try {
|
|
5765
|
+
await pnpmAdd(profileDir, specs.length === 1 ? specs[0] : specs, registries?.[0])
|
|
5766
|
+
method = 'pnpm-add'
|
|
5767
|
+
} catch (error) {
|
|
5768
|
+
errors.push(`pnpm add 对齐失败:${error instanceof Error ? error.message : String(error)}`)
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5771
|
+
rows = snap()
|
|
5772
|
+
drifted = drift(rows)
|
|
5773
|
+
}
|
|
5774
|
+
const lockUpdated = drifted.length === 0
|
|
5775
|
+
return {
|
|
5776
|
+
method,
|
|
5777
|
+
packages: rows.map((r) => ({ name: r.name, installedVersion: r.installed, lockVersion: r.lock, aligned: r.installed !== null && r.installed === r.lock })),
|
|
5778
|
+
spec: rows.length === 1 ? rows[0].spec : null,
|
|
5779
|
+
installedVersion: rows.length === 1 ? rows[0].installed : null,
|
|
5780
|
+
lockVersion: rows.length === 1 ? rows[0].lock : null,
|
|
5781
|
+
lockUpdated,
|
|
5782
|
+
lockNote: lockUpdated
|
|
5783
|
+
? null
|
|
5784
|
+
: `${drifted.length} 个包没写进 pnpm-lock.yaml(${drifted.map((r) => `${r.name}:装了 ${r.installed ?? '未知'}/lock 里是 ${r.lock ?? '未记录'}`).join(';')}):之后任何 pnpm 操作(开关插件、dsh plugin add/remove)都可能把它们还原。要钉住请执行:${command(drifted[0].name, drifted[0].installed)}`,
|
|
5785
|
+
command: drifted.length > 0 ? command(drifted[0].name, drifted[0].installed) : command(rows[0]?.name ?? '', rows[0]?.installed ?? null),
|
|
5786
|
+
errors,
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5697
5790
|
export function apply(ctx) {
|
|
5698
5791
|
// 恢复 AI 赋能任务(restart 后 running 置失败,plan-ready/结果保留)
|
|
5699
5792
|
loadAiJobs()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.58",
|
|
4
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
5
|
"repository": {
|
|
6
6
|
"type": "git",
|