@weibaohui/dsh-git-server 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/package.json +3 -3
  3. package/src/index.js +73 -69
package/README.md CHANGED
@@ -43,3 +43,13 @@ dsh plugin --profile web add @weibaohui/dsh-git-server -w
43
43
  ## 联系我 :飞书群
44
44
 
45
45
  ![link](https://foruda.gitee.com/images/1774880015525784725/4fd67005_77493.png "link")
46
+
47
+ ## 版本兼容性
48
+
49
+ 本插件与 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`@deepseek-ai/dsh`)的版本对应关系:
50
+
51
+ | 插件版本 | 适配 dsh 版本 | 备注 |
52
+ |---------|--------------|------|
53
+ | 0.1.3 | 0.1.7-rc.2 | 当前版本,已在 @deepseek-ai/dsh@0.1.7-rc.2 下验证运行 |
54
+
55
+ > **发版约定**:每次发布新版本时,请在上表追加一行,记录该插件版本实际验证所用的 `@deepseek-ai/dsh` 版本。`package.json` 的 `engines.dsh` 声明最低支持版本;本表记录实际验证版本,二者配合使用。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@weibaohui/dsh-git-server",
3
- "version": "0.1.2",
4
- "description": "dsh 插件 · Git 服务器:内嵌 ts-gogs(Gogs 的 TypeScript 平替),独立端口跑完整 Git 服务(HTTP clone/push、网页端、issue/PR/wiki),可复用 user-management 的用户名密码,设置页一键启停。",
3
+ "version": "0.1.3",
4
+ "description": "dsh 插件 · Git 服务器:独立端口跑完整 Git 服务(HTTP clone/push、网页端、issue/PR/wiki/发版/标签/里程碑/webhook),可复用 user-management 账号,局域网可 clone,设置页一键启停、崩溃自动拉起。",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/weibaohui/dsh-git-server.git"
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "engines": {
28
28
  "node": ">=22.5",
29
- "dsh": ">=0.1.2-rc.1"
29
+ "dsh": ">=0.1.7-rc.2"
30
30
  },
31
31
  "dsh": {
32
32
  "bundle": {
package/src/index.js CHANGED
@@ -29,13 +29,13 @@ const inject = ['webServer', 'settings', 'connection']
29
29
  const API_PREFIX = '/' + engine.PLUGIN_ID + '/api'
30
30
  const SETTINGS_NS = engine.PLUGIN_ID
31
31
 
32
- // ── schemastery 加载(settings 服务同源;缺席时仅 loader config 生效)──────
32
+ // ── schemastery:从宿主 dsh 全局安装的 vendored 副本同步加载(0.1.7 起
33
+ // settings 服务通过 entry.fiber.runtime.Config 自动发现 schema,不再支持
34
+ // ctx.settings.register,故必须在模块顶层同步构建导出的 Config)。加载
35
+ // 失败则 Config 缺席,插件仍可运行(退回进程内兜底)。
33
36
 
34
- const { pathToFileURL } = require('node:url')
35
37
  const os = require('node:os')
36
38
 
37
- let schemaPromise = null
38
-
39
39
  function hostCandidatePaths(pkgName, rel) {
40
40
  const prefixes = [process.env.DSH_GLOBAL_PREFIX, os.homedir() + '/.local'].filter(Boolean)
41
41
  return prefixes.map((prefix) =>
@@ -43,64 +43,54 @@ function hostCandidatePaths(pkgName, rel) {
43
43
  )
44
44
  }
45
45
 
46
- function startSchemaLoader() {
47
- if (!schemaPromise) {
48
- schemaPromise = (async () => {
49
- const candidates = [
50
- ...hostCandidatePaths('schemastery', 'lib/index.cjs'),
51
- '@deepseek-ai/schemastery',
52
- ]
53
- const errors = []
54
- for (const target of candidates) {
55
- try {
56
- const specifier = target.includes('/') && !target.startsWith('@') && target.includes('node_modules')
57
- ? pathToFileURL(target).href
58
- : target
59
- const mod = await import(specifier)
60
- return mod
61
- } catch (e) {
62
- errors.push(`${target}: ${String((e && e.message) || e).slice(0, 160)}`)
63
- }
64
- }
65
- throw new Error(errors.join(' | '))
66
- })()
46
+ // 同步加载 schemastery(CJS 副本优先,与宿主 settings 服务同源)。
47
+ function loadSchemaSync() {
48
+ for (const target of hostCandidatePaths('schemastery', 'lib/index.cjs')) {
49
+ try { return require(target) } catch {}
67
50
  }
68
- return schemaPromise
51
+ try { return require('@deepseek-ai/schemastery') } catch {}
52
+ return null
69
53
  }
70
54
 
71
- async function resolveSchema() {
72
- try {
73
- const mod = await startSchemaLoader()
74
- if (!mod) return null
75
- const Schema = mod.default || mod.Schema || (typeof mod === 'function' ? mod : null)
76
- return Schema && typeof Schema.object === 'function' ? Schema : null
77
- } catch {
78
- return null
79
- }
55
+ // 测试缝隙:预先注入 Schema,让单测不依赖本机宿主安装
56
+ let __schemaOverride = null
57
+ function __seedSchema(Schema) {
58
+ __schemaOverride = Schema
59
+ Config = settingsSchema(Schema)
60
+ module.exports.Config = Config
80
61
  }
81
62
 
82
- function __seedSchema(Schema) {
83
- schemaPromise = Promise.resolve(Schema ? { default: Schema } : null)
63
+ // 兼容旧调用方(单测):异步解析到同一份 Schema 或 null
64
+ async function resolveSchema() {
65
+ const S = __schemaOverride || loadSchemaSync()
66
+ return S && typeof S.object === 'function' ? S : null
84
67
  }
85
68
 
86
- function settingsSchema(Schema) {
87
- if (!Schema || typeof Schema.object !== 'function') return null
88
- return Schema.object({
89
- enabled: Schema.boolean().default(engine.DEFAULTS.enabled),
90
- port: Schema.number().step(1).min(engine.NUM_RANGES.port[0]).max(engine.NUM_RANGES.port[1]).default(engine.DEFAULTS.port),
91
- dataDir: Schema.string().default(engine.DEFAULTS.dataDir),
92
- adminPassword: Schema.string().default(engine.DEFAULTS.adminPassword),
93
- impersonateSecret: Schema.string().default(engine.DEFAULTS.impersonateSecret),
69
+ function settingsSchema(S) {
70
+ if (!S || typeof S.object !== 'function') return null
71
+ // 字段标 .volatile():0.1.7 settings 服务只把 volatile 字段投进设置 UI,
72
+ // 且只有 volatile 字段可经 ctx.settings.update 在线写回。
73
+ return S.object({
74
+ enabled: S.boolean().default(engine.DEFAULTS.enabled).volatile(),
75
+ port: S.number().step(1).min(engine.NUM_RANGES.port[0]).max(engine.NUM_RANGES.port[1]).default(engine.DEFAULTS.port).volatile(),
76
+ dataDir: S.string().default(engine.DEFAULTS.dataDir).volatile(),
77
+ adminPassword: S.string().default(engine.DEFAULTS.adminPassword).volatile(),
78
+ impersonateSecret: S.string().default(engine.DEFAULTS.impersonateSecret).volatile(),
94
79
  })
95
80
  }
96
81
 
82
+ // 0.1.7 settings 服务自动发现模块导出的 Config(entry.fiber.runtime.Config)。
83
+ const Schema = __schemaOverride || loadSchemaSync()
84
+ let Config = settingsSchema(Schema)
85
+
97
86
  // ── 插件 ─────────────────────────────────────────────────────────────────
98
87
 
99
88
  module.exports = {
100
89
  name,
101
90
  inject,
91
+ Config,
102
92
  version: '0.1.0',
103
- __internals: { settingsSchema, resolveSchema, __seedSchema, API_PREFIX, SETTINGS_NS },
93
+ __internals: { settingsSchema, loadSchemaSync, resolveSchema, __seedSchema, API_PREFIX, SETTINGS_NS },
104
94
 
105
95
  apply(ctx, config) {
106
96
  // 宿主可能过滤插件 logger 输出;console.error 走 stderr 保底可见(launchd 下进 err.log)
@@ -113,8 +103,8 @@ module.exports = {
113
103
  const webServer = ctx.webServer
114
104
 
115
105
  const base = engine.normalizeConfig({ ...engine.DEFAULTS, ...(config || {}) })
116
- let settingsScope = null
117
- let memoryPatch = {}
106
+ let liveSettings = {} // 0.1.7:settings 文档里的实时 volatile 值(事件驱动刷新)
107
+ let memoryPatch = {} // settings 写回失败时的进程内兜底
118
108
  let pwFileCache = null
119
109
 
120
110
  const state = { handle: null, fingerprint: '', error: null, restarting: 0, deps: 'ok' }
@@ -155,9 +145,9 @@ module.exports = {
155
145
  return
156
146
  }
157
147
  const pw = engine.generateSecret().slice(0, 20)
158
- if (settingsScope && typeof settingsScope.update === 'function') {
148
+ if (ctx.settings && typeof ctx.settings.update === 'function') {
159
149
  try {
160
- await settingsScope.update({ adminPassword: pw })
150
+ await ctx.settings.update(SETTINGS_NS, { adminPassword: pw })
161
151
  } catch (e) {
162
152
  logger.warn(`dsh-git-server: 管理员密码写入 settings 失败,退回文件: ${(e && e.message) || e}`)
163
153
  memoryPatch.adminPassword = pw
@@ -253,8 +243,8 @@ module.exports = {
253
243
  memoryPatch.impersonateSecret = pw
254
244
  cfg.impersonateSecret = pw
255
245
  if (fromFile) return
256
- if (settingsScope && typeof settingsScope.update === 'function') {
257
- try { await settingsScope.update({ impersonateSecret: pw }) } catch (e) {
246
+ if (ctx.settings && typeof ctx.settings.update === 'function') {
247
+ try { await ctx.settings.update(SETTINGS_NS, { impersonateSecret: pw }) } catch (e) {
258
248
  logger.warn(`dsh-git-server: impersonate secret 写入 settings 失败,退回文件: ${(e && e.message) || e}`)
259
249
  writeImpersonateFile(pw)
260
250
  }
@@ -317,11 +307,20 @@ module.exports = {
317
307
  }
318
308
  }
319
309
 
310
+ // 0.1.7:读取本命名空间在 settings 文档里的实时值(describe 投影后的 volatile 字段)。
311
+ // 没有 Config 或 settings 服务缺席时返回 {},effective() 退回 base + memoryPatch。
312
+ function readLiveSettings() {
313
+ try {
314
+ if (!ctx.settings || typeof ctx.settings.describe !== 'function') return {}
315
+ const d = ctx.settings.describe().find((x) => x.ns === SETTINGS_NS)
316
+ return d && d.value ? d.value : {}
317
+ } catch {
318
+ return {}
319
+ }
320
+ }
321
+
320
322
  function effective() {
321
- const fromSettings = settingsScope && typeof settingsScope.get === 'function'
322
- ? settingsScope.get()
323
- : null
324
- return engine.normalizeConfig({ ...base, ...(fromSettings || {}), ...memoryPatch })
323
+ return engine.normalizeConfig({ ...base, ...liveSettings, ...memoryPatch })
325
324
  }
326
325
 
327
326
  let restartTimer = null
@@ -384,21 +383,26 @@ module.exports = {
384
383
  return status()
385
384
  }
386
385
 
387
- // ── settings 注册(异步;缺席不阻塞宿主半其余功能)───────────────────
386
+ // ── settings 接线(0.1.7:导出 Config 即自动注册 schema + 自动生成设置
387
+ // 页面;这里只订阅文档变更刷新缓存,再 reconcile。settings 服务缺席或
388
+ // Config 未构建时退回进程内兜底,不阻塞宿主半其余功能。)──────────────
388
389
  ;(async () => {
389
- const Schema = await resolveSchema()
390
- if (Schema && ctx.settings && typeof ctx.settings.register === 'function') {
391
- try {
392
- settingsScope = ctx.settings.register(SETTINGS_NS, settingsSchema(Schema), { base })
393
- logger.info('dsh-git-server: settings-registered')
394
- } catch (e) {
395
- logger.warn(`dsh-git-server: settings register 失败(仅 loader config 生效): ${(e && e.message) || e}`)
396
- }
397
- } else {
390
+ liveSettings = readLiveSettings()
391
+ if (!Config) {
398
392
  logger.warn(
399
- `dsh-git-server: settings 不可用(Schema=${Boolean(Schema)}, ctx.settings=${Boolean(ctx.settings)})——配置退回进程内兜底`,
393
+ `dsh-git-server: Config schema 未构建(schemastery 未加载)——配置退回进程内兜底`,
400
394
  )
401
395
  }
396
+ if (ctx.on && typeof ctx.on === 'function') {
397
+ ctx.effect(() => {
398
+ const off = ctx.on('settings/document-updated', (ns) => {
399
+ if (ns !== SETTINGS_NS) return
400
+ liveSettings = readLiveSettings()
401
+ reconcile().catch((e) => logger.error(`dsh-git-server: reconcile: ${(e && e.message) || e}`))
402
+ })
403
+ return () => { try { off() } catch {} }
404
+ }, 'dsh-git-server: settings watch')
405
+ }
402
406
  await reconcile()
403
407
  })().catch((e) => logger.error(`dsh-git-server: init: ${(e && e.message) || e}`))
404
408
 
@@ -654,9 +658,9 @@ module.exports = {
654
658
  const patch = engine.sanitizePatch(patchBody, effective())
655
659
  memoryPatch = { ...memoryPatch, ...patch }
656
660
  if (typeof patch.adminPassword === 'string' && patch.adminPassword) writePasswordFile(patch.adminPassword)
657
- if (settingsScope && typeof settingsScope.update === 'function') {
661
+ if (ctx.settings && typeof ctx.settings.update === 'function') {
658
662
  try {
659
- await settingsScope.update(patch)
663
+ await ctx.settings.update(SETTINGS_NS, patch)
660
664
  } catch (e) {
661
665
  logger.warn(`dsh-git-server: settings update 失败(仅本次进程生效): ${(e && e.message) || e}`)
662
666
  }