@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 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.56`**, maintainability only;
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.56`, or `main` of this repo.
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.56` 等价**,只为可维护性;
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.56`,或本仓库 `main`。
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 (!/^(?:packages|examples|plugins|skills|apps|extensions|src|lib)\/[^/]+\/package\.json$/u.test(hitPath)) continue
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' && /^(?:packages|examples|plugins|skills|apps|extensions|src|lib)\/[^/]+\/package\.json$/u.test(node.path))
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 && !subpackageMode && !expanded) {
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 (!subpackageMode && !expanded) {
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 (!subpackageMode && !expanded) {
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 (!subpackageMode && !expanded) {
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.56",
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",