@noob-stupid/dsh-plugin-console 0.5.17 → 0.5.19

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.
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { installJobView } from '../domain/install.js'
5
5
  import { runInstallJob } from '../domain/install-job.js'
6
- import { githubRepoInfo } from '../domain/market.js'
6
+ import { fetchRepoPackageEx, githubRepoInfo, subpackageCandidates } from '../domain/market.js'
7
7
  import { runSkillInstallJob } from '../domain/skills.js'
8
8
  import { probeGitmodules, resolveInstallKind, runSuiteInstallJob } from '../domain/suite.js'
9
9
  import { sendError, sendJson } from '../infra/httpd.js'
@@ -83,10 +83,15 @@ async function routeInstall(req, res, rc) {
83
83
  // 套装通道兜底:探测说有、clone 下来却没有 .gitmodules(假阳性 / 仓库已重构)时
84
84
  // 回落普通插件安装,而不是给用户一个「未找到 .gitmodules」的失败。
85
85
  const runSuiteThenFallback = async () => {
86
- const result = await runSuiteInstallJob(job, ctx)
86
+ // 0.5.19:套装作业入口自己也有一道判据(根包/子包已发布 → 不克隆整个仓库),它需要真实探测:
87
+ // 显式「安装套装」(前端 hasSuite=true 的卡片)走的就是这条路,不经过 install-job 的候选循环。
88
+ const result = await runSuiteInstallJob(job, ctx, {
89
+ probes: { fetchRepoPackageEx, subpackageCandidates, probeGitmodules },
90
+ })
87
91
  if (result?.notASuite !== true) return
88
92
  job.kind = 'plugin'
89
- job.suiteNote = '仓库实际内容与探测不符(没有 .gitmodules),已自动回落普通插件安装'
93
+ // 回落文案一律用 result.reason:克隆失败与"内容不符"是两回事,不能都报成后者(用户会被带偏)
94
+ job.suiteNote = result.reason ?? '仓库实际内容与探测不符(没有 .gitmodules),已自动回落普通插件安装'
90
95
  await runInstallJob(job, ctx)
91
96
  }
92
97
  // 后台执行:请求立即返回,安装不受客户端断开/离开面板影响
@@ -401,6 +401,85 @@ async function routeSources(req, res, rc) {
401
401
  sendJson(res, 200, { ok: true, sources: maskSources(sources) })
402
402
  return
403
403
  }
404
+ if (action === 'add-archive') {
405
+ // archive 通道源(批次 C-⑨):模板必须含 {owner}/{repo}({branch} 可选)
406
+ const name = typeof body.name === 'string' ? body.name.trim() : ''
407
+ const urlTemplate = typeof body.urlTemplate === 'string' ? body.urlTemplate.trim() : ''
408
+ if (!urlTemplate.includes('{owner}') || !urlTemplate.includes('{repo}')) {
409
+ sendError(res, 400, 'archive 源模板必须同时包含 {owner} 与 {repo} 占位符({branch} 可选)')
410
+ return
411
+ }
412
+ if (!isAllowedGitSourceUrl(urlTemplate)) {
413
+ sendError(res, 400, 'archive 源地址必须是 https://(或本机/私网 http://、file://)的合法 URL')
414
+ return
415
+ }
416
+ if ((sources.archiveSources ?? []).some((s) => s.urlTemplate === urlTemplate)) {
417
+ sendError(res, 400, '该 archive 源已存在')
418
+ return
419
+ }
420
+ sources.archiveSources = [...(sources.archiveSources ?? []), {
421
+ id: `arc-${Date.now().toString(36)}`,
422
+ name: name || urlTemplate,
423
+ urlTemplate,
424
+ primary: (sources.archiveSources ?? []).length === 0,
425
+ }]
426
+ await writeSources(sources)
427
+ sendJson(res, 200, { ok: true, sources: maskSources(sources) })
428
+ return
429
+ }
430
+ if (action === 'edit-archive') {
431
+ const id = typeof body.id === 'string' ? body.id : ''
432
+ const name = typeof body.name === 'string' ? body.name.trim() : ''
433
+ const urlTemplate = typeof body.urlTemplate === 'string' ? body.urlTemplate.trim() : ''
434
+ if (!urlTemplate.includes('{owner}') || !urlTemplate.includes('{repo}')) {
435
+ sendError(res, 400, 'archive 源模板必须同时包含 {owner} 与 {repo} 占位符({branch} 可选)')
436
+ return
437
+ }
438
+ if (!isAllowedGitSourceUrl(urlTemplate)) {
439
+ sendError(res, 400, 'archive 源地址必须是 https://(或本机/私网 http://、file://)的合法 URL')
440
+ return
441
+ }
442
+ const target = (sources.archiveSources ?? []).find((s) => s.id === id)
443
+ if (!target) {
444
+ sendError(res, 404, '没有这个 archive 源')
445
+ return
446
+ }
447
+ target.name = name || urlTemplate
448
+ target.urlTemplate = urlTemplate
449
+ await writeSources(sources)
450
+ sendJson(res, 200, { ok: true, sources: maskSources(sources) })
451
+ return
452
+ }
453
+ if (action === 'set-archive-primary') {
454
+ const id = typeof body.id === 'string' ? body.id : ''
455
+ const list = sources.archiveSources ?? []
456
+ if (!list.some((s) => s.id === id)) {
457
+ sendError(res, 404, '没有这个 archive 源')
458
+ return
459
+ }
460
+ for (const s of list) s.primary = s.id === id
461
+ await writeSources(sources)
462
+ sendJson(res, 200, { ok: true, sources: maskSources(sources) })
463
+ return
464
+ }
465
+ if (action === 'remove-archive') {
466
+ const id = typeof body.id === 'string' ? body.id : ''
467
+ const list = sources.archiveSources ?? []
468
+ if (!list.some((s) => s.id === id)) {
469
+ sendError(res, 404, '没有这个 archive 源')
470
+ return
471
+ }
472
+ const rest = list.filter((s) => s.id !== id)
473
+ if (rest.length === 0) {
474
+ sendError(res, 400, '至少保留一个 archive 源(可先添加自建镜像再删除默认源)')
475
+ return
476
+ }
477
+ if (!rest.some((s) => s.primary)) rest[0].primary = true
478
+ sources.archiveSources = rest
479
+ await writeSources(sources)
480
+ sendJson(res, 200, { ok: true, sources: maskSources(sources) })
481
+ return
482
+ }
404
483
  if (action === 'reset') {
405
484
  const defaults = JSON.parse(JSON.stringify(DEFAULT_SOURCES))
406
485
  setMarketIndexCache(null)
@@ -430,7 +509,7 @@ async function routeSources(req, res, rc) {
430
509
  sendJson(res, 200, { ok: true, sources: maskSources(sources) })
431
510
  return
432
511
  }
433
- sendError(res, 400, '未知操作(add / edit / set-primary / remove / add-search / remove-search / add-index / edit-index / set-index-primary / remove-index / set-index-merge / add-git / edit-git / set-git-primary / remove-git / gitee-setup / gitee-clear / reset)')
512
+ sendError(res, 400, '未知操作(add / edit / set-primary / remove / add-search / remove-search / add-index / edit-index / set-index-primary / remove-index / set-index-merge / add-git / edit-git / set-git-primary / remove-git / add-archive / edit-archive / set-archive-primary / remove-archive / gitee-setup / gitee-clear / reset)')
434
513
  return
435
514
  }
436
515
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noob-stupid/dsh-plugin-console",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "description": "DSH 框架升级安全与插件升级门控:一键升级、失败自动回滚、升级后回滚上版、旧插件不适配自动禁用;内置多源插件市场为发现层,插件源全部可自定义,可指向公司内网私有源 / 私有索引 / 本地 Git 仓库,纯内网离线可用 | Framework upgrade safety & plugin version gating for DSH, with a customizable multi-source plugin market: point every source at internal mirrors or a local file:// repo for intranet-only, offline installs.",
5
5
  "repository": {
6
6
  "type": "git",