@noob-stupid/dsh-plugin-console 0.3.55 → 0.3.57

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.55`**, 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.55`, or `main` of this repo.
314
+ - **For everyday use** stick to npm `@noob-stupid/dsh-plugin-console@0.3.57`, 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.55` 等价**,只为可维护性;
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.55`,或本仓库 `main`。
282
+ - **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console@0.3.57`,或本仓库 `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
@@ -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
  }
@@ -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'
@@ -5492,6 +5509,20 @@ async function runInstallJob(job, ctx) {
5492
5509
  const adapt = await maybeAutoAdaptCompat({ profileDir, packageName: installedName, syncedNames, patchPath, ctx })
5493
5510
  if (adapt !== null && adapt.ran === true) job.compatNote = adapt.note
5494
5511
  } catch {}
5512
+ // lock 对账(2026-09-21,用户报告的自更新缺陷同源):主包 + 本次被补装/对齐的聚合子包一起核对,
5513
+ // 一次 pnpm add 把漂移的包全写进 lock;对不上就留 lockNote,让面板如实告知(不假装成功)。
5514
+ try {
5515
+ const lock = await reconcileLockfile({
5516
+ profileDir,
5517
+ packages: [installedName, ...syncedNames].map((name) => ({ name })),
5518
+ registries,
5519
+ })
5520
+ job.lockUpdated = lock.lockUpdated
5521
+ job.lockVersion = lock.lockVersion
5522
+ job.lockMethod = lock.method
5523
+ job.lockPackages = lock.packages
5524
+ if (lock.lockNote !== null) job.lockNote = lock.lockNote
5525
+ } catch {}
5495
5526
  job.stage = 'configuring'
5496
5527
  // 2026-09-06 事故(i18n 更新变重复行):@linxin666/dsh-i18n 声明了 dsh.bundle.patch,
5497
5528
  // 更新流程按「bundle 安装规则」把它追加进 bundles → 与全家桶内的 web-ui-i18n 行(同包)重复。
@@ -5694,6 +5725,63 @@ export async function selfUpdateToLatest({ profileDir, latest, registries, curlM
5694
5725
  return { method, spec, installedVersion: installed, lockVersion: lock, lockUpdated, lockNote, command, note: noteParts.join(';'), errors }
5695
5726
  }
5696
5727
 
5728
+
5729
+ /**
5730
+ * 通用 lock 对账(**任何**安装通道装完都该调用,支持一次对账多个包)。
5731
+ * 走非 pnpm 通道(并行 curl / curl tarball / GitHub Release / git 装配 / 套装 copyTree / 聚合子包补装)装的包,
5732
+ * node_modules 里的版本与 `pnpm-lock.yaml` 记的版本可能不一致——之后任何 pnpm 操作(开关插件改
5733
+ * `dsh.profile.bundles`、`dsh plugin add/remove`)都可能按 lock 把它还原或当外来物处理。这里:
5734
+ * ① 全部一致 → 直接返回(不跑 pnpm,零成本);
5735
+ * ② 有漂移 → **一次** `pnpm add <pkg1>@<v1> <pkg2>@<v2> …` 把漂移包精确写进 lock;
5736
+ * ③ 仍对不上 → 返回 lockNote(逐包列出)+ 可复制命令,由调用方如实展示,绝不假装成功。
5737
+ */
5738
+ export async function reconcileLockfile({ profileDir, packageName = null, packages = null, registries = [], pnpmAdd = pnpmInstall, execOpts = {} }) {
5739
+ const targets = Array.isArray(packages)
5740
+ ? packages.filter((p) => p && typeof p.name === 'string' && p.name !== '')
5741
+ : (packageName === null ? [] : [{ name: packageName, version: null }])
5742
+ const unique = []
5743
+ for (const t of targets) if (!unique.some((u) => u.name === t.name)) unique.push(t)
5744
+ const command = (name, version) => `dsh plugin --profile <你的profile> add ${name}@${version ?? '<版本>'}`
5745
+ const snap = () => unique.map((t) => ({
5746
+ name: t.name,
5747
+ spec: profileSpecOf(profileDir, t.name),
5748
+ installed: readInstalledVersion(profileDir, t.name),
5749
+ lock: lockVersionOf(profileDir, t.name),
5750
+ }))
5751
+ const drift = (rows) => rows.filter((r) => r.installed === null || r.installed !== r.lock)
5752
+ let rows = snap()
5753
+ let drifted = drift(rows)
5754
+ const errors = []
5755
+ let method = null
5756
+ if (drifted.length > 0) {
5757
+ const specs = drifted.filter((r) => r.installed !== null).map((r) => `${r.name}@${r.installed}`)
5758
+ if (specs.length > 0) {
5759
+ try {
5760
+ await pnpmAdd(profileDir, specs.length === 1 ? specs[0] : specs, registries?.[0])
5761
+ method = 'pnpm-add'
5762
+ } catch (error) {
5763
+ errors.push(`pnpm add 对齐失败:${error instanceof Error ? error.message : String(error)}`)
5764
+ }
5765
+ }
5766
+ rows = snap()
5767
+ drifted = drift(rows)
5768
+ }
5769
+ const lockUpdated = drifted.length === 0
5770
+ return {
5771
+ method,
5772
+ packages: rows.map((r) => ({ name: r.name, installedVersion: r.installed, lockVersion: r.lock, aligned: r.installed !== null && r.installed === r.lock })),
5773
+ spec: rows.length === 1 ? rows[0].spec : null,
5774
+ installedVersion: rows.length === 1 ? rows[0].installed : null,
5775
+ lockVersion: rows.length === 1 ? rows[0].lock : null,
5776
+ lockUpdated,
5777
+ lockNote: lockUpdated
5778
+ ? null
5779
+ : `${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)}`,
5780
+ command: drifted.length > 0 ? command(drifted[0].name, drifted[0].installed) : command(rows[0]?.name ?? '', rows[0]?.installed ?? null),
5781
+ errors,
5782
+ }
5783
+ }
5784
+
5697
5785
  export function apply(ctx) {
5698
5786
  // 恢复 AI 赋能任务(restart 后 running 置失败,plan-ready/结果保留)
5699
5787
  loadAiJobs()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noob-stupid/dsh-plugin-console",
3
- "version": "0.3.55",
3
+ "version": "0.3.57",
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",