@weibaohui/dsh-git-server 0.1.2 → 0.1.4
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 +11 -0
- package/client/bundle.js +60 -6
- package/client/index.js +60 -6
- package/package.json +3 -3
- package/src/index.js +73 -69
package/README.md
CHANGED
|
@@ -43,3 +43,14 @@ dsh plugin --profile web add @weibaohui/dsh-git-server -w
|
|
|
43
43
|
## 联系我 :飞书群
|
|
44
44
|
|
|
45
45
|

|
|
46
|
+
|
|
47
|
+
## 版本兼容性
|
|
48
|
+
|
|
49
|
+
本插件与 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`@deepseek-ai/dsh`)的版本对应关系:
|
|
50
|
+
|
|
51
|
+
| 插件版本 | 适配 dsh 版本 | 备注 |
|
|
52
|
+
|---------|--------------|------|
|
|
53
|
+
| 0.1.4 | 0.1.7-rc.2 | 当前版本,已在 @deepseek-ai/dsh@0.1.7-rc.2 下验证运行 |
|
|
54
|
+
| 0.1.3 | 0.1.7-rc.2 | 已在 @deepseek-ai/dsh@0.1.7-rc.2 下验证运行 |
|
|
55
|
+
|
|
56
|
+
> **发版约定**:每次发布新版本时,请在上表追加一行,记录该插件版本实际验证所用的 `@deepseek-ai/dsh` 版本。`package.json` 的 `engines.dsh` 声明最低支持版本;本表记录实际验证版本,二者配合使用。
|
package/client/bundle.js
CHANGED
|
@@ -12,8 +12,8 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
* @weibaohui/dsh-git-server — Browser half(原生 dsh 插件 UI,无 iframe/桥接)。
|
|
13
13
|
*
|
|
14
14
|
* 两个面:
|
|
15
|
-
* 1. sidebar.footer.action「Git」→
|
|
16
|
-
* 仓库浏览:文件树/文件内容/提交/分支/工单[列表/新建/评论/开关])。
|
|
15
|
+
* 1. sidebar.footer.action「Git」→ 中栏接管管理页(taskboard 同款,盖会话列不盖侧栏;
|
|
16
|
+
* 仓库列表/建仓/删除 + 仓库浏览:文件树/文件内容/提交/分支/工单[列表/新建/评论/开关])。
|
|
17
17
|
* 2. settings.section「Git 服务器」→ 服务配置(启停/端口/数据目录/兜底密码)。
|
|
18
18
|
*
|
|
19
19
|
* 数据通道:宿主同源路由 /dsh-git-server/api/*(dsh 登录门禁保护,
|
|
@@ -37,6 +37,12 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
// ── locale ────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
39
|
const NS = 'dshGitServer'
|
|
40
|
+
// 中栏接管(taskboard 同款)开合属性 + 互斥协议
|
|
41
|
+
const DGS_ACTIVE_ATTR = 'data-dsh-git-active'
|
|
42
|
+
const DGS_PANEL_NAME = 'dsh-git'
|
|
43
|
+
const DGS_ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
44
|
+
const DGS_OTHER_ACTIVE_ATTRS = ['data-dsh-atb-active', 'data-dsh-taskboard-active', 'data-dsh-ssh-active', 'data-dsh-prc-active', 'data-dsh-kb-active']
|
|
45
|
+
const DGS_SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="newSession"]'
|
|
40
46
|
const ZH = {
|
|
41
47
|
nav: 'Git', title: '代码仓库',
|
|
42
48
|
myRepos: '我的仓库', newRepo: '新建仓库', repoName: '仓库名', private: '私有',
|
|
@@ -94,7 +100,16 @@ window.__ModuleLoader__.load({
|
|
|
94
100
|
holder.id = 'dgs-styles'
|
|
95
101
|
holder.style.display = 'none'
|
|
96
102
|
holder.innerHTML = `<style>
|
|
97
|
-
|
|
103
|
+
/* 中栏接管视图(taskboard 同款):容器挂进会话列末尾,html 属性驱动开合,只隐藏列内兄弟。
|
|
104
|
+
* 三代壳层选择器:dev shell data-pane / 官方 CSS-Module centerCol / Desktop 扩展框。 */
|
|
105
|
+
.dsh-git-view{display:none}
|
|
106
|
+
html[data-dsh-git-active] [data-pane="conversation"] > *:not([data-dsh-git-view]),
|
|
107
|
+
html[data-dsh-git-active] [class*="centerCol"] > *:not([data-dsh-git-view]),
|
|
108
|
+
html[data-dsh-git-active] .dshDesktopConversationSurface > *:not([data-dsh-git-view]){display:none !important}
|
|
109
|
+
html[data-dsh-git-active] .dsh-git-view{display:flex;flex-direction:column;height:100%;overflow:hidden}
|
|
110
|
+
/* 壳层退化(会话列缺席):容器兜底挂 body 时退回全屏浮层,保证入口点击永远有响应 */
|
|
111
|
+
.dsh-git-view[data-dsh-git-fallback]{position:fixed;inset:0;z-index:2147483000;display:flex}
|
|
112
|
+
.dgs-page{position:relative;flex:1;min-height:0;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);font-size:14px}
|
|
98
113
|
.dgs-head{display:flex;align-items:center;gap:12px;padding:12px 28px;border-bottom:1px solid var(--dsw-alias-border-l2);flex:none}
|
|
99
114
|
.dgs-h1{font-size:16px;font-weight:700;margin:0}
|
|
100
115
|
.dgs-close{margin-left:auto;cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font-size:18px;padding:4px 8px;border-radius:8px}
|
|
@@ -2050,7 +2065,7 @@ window.__ModuleLoader__.load({
|
|
|
2050
2065
|
|
|
2051
2066
|
// ── 主页(仓库列表 + 建仓) ────────────────────────────────────────────────
|
|
2052
2067
|
|
|
2053
|
-
function GitPage({ onClose, t }) {
|
|
2068
|
+
function GitPage({ onClose, t, standalone }) {
|
|
2054
2069
|
useEffect(ensureStyles, [])
|
|
2055
2070
|
const route = useHashRoute()
|
|
2056
2071
|
const [me, setMe] = useState(null)
|
|
@@ -2069,6 +2084,33 @@ window.__ModuleLoader__.load({
|
|
|
2069
2084
|
window.addEventListener('keydown', onKey)
|
|
2070
2085
|
return () => window.removeEventListener('keydown', onKey)
|
|
2071
2086
|
}, [onClose])
|
|
2087
|
+
// 中栏接管开合:挂载即打开 → html[data-dsh-git-active] + 广播互斥事件;卸载(关闭)时清理。
|
|
2088
|
+
// standalone(__boot 开发挂载)不动全局。
|
|
2089
|
+
useEffect(() => {
|
|
2090
|
+
if (standalone) return undefined
|
|
2091
|
+
for (const attr of DGS_OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
|
|
2092
|
+
document.documentElement.setAttribute(DGS_ACTIVE_ATTR, '')
|
|
2093
|
+
document.dispatchEvent(new CustomEvent(DGS_ACTIVATE_EVENT, { detail: DGS_PANEL_NAME }))
|
|
2094
|
+
return () => { document.documentElement.removeAttribute(DGS_ACTIVE_ATTR) }
|
|
2095
|
+
}, [standalone])
|
|
2096
|
+
useEffect(() => {
|
|
2097
|
+
if (standalone) return undefined
|
|
2098
|
+
const onOther = (e) => { if (e && e.detail !== DGS_PANEL_NAME) onClose() }
|
|
2099
|
+
document.addEventListener(DGS_ACTIVATE_EVENT, onOther)
|
|
2100
|
+
return () => document.removeEventListener(DGS_ACTIVATE_EVENT, onOther)
|
|
2101
|
+
}, [standalone, onClose])
|
|
2102
|
+
// 点侧栏会话行自动关面板(自家入口子树豁免)
|
|
2103
|
+
useEffect(() => {
|
|
2104
|
+
if (standalone) return undefined
|
|
2105
|
+
const onClickRow = (e) => {
|
|
2106
|
+
const target = e.target
|
|
2107
|
+
if (!(target instanceof Element)) return
|
|
2108
|
+
if (target.closest('[' + GIT_ENTRY_ATTR + ']')) return
|
|
2109
|
+
if (target.closest(DGS_SIDEBAR_ROW_SELECTOR)) onClose()
|
|
2110
|
+
}
|
|
2111
|
+
document.addEventListener('click', onClickRow, true)
|
|
2112
|
+
return () => document.removeEventListener('click', onClickRow, true)
|
|
2113
|
+
}, [standalone, onClose])
|
|
2072
2114
|
const notify = (m) => { setToast(m); setTimeout(() => setToast(''), 1600) }
|
|
2073
2115
|
const seg = route.split('/').filter(Boolean)
|
|
2074
2116
|
const view = seg[0] || 'repos'
|
|
@@ -2214,10 +2256,21 @@ window.__ModuleLoader__.load({
|
|
|
2214
2256
|
try { gitPageHost.el.remove() } catch {}
|
|
2215
2257
|
gitPageHost = null
|
|
2216
2258
|
}
|
|
2259
|
+
function gitConversationColumn() {
|
|
2260
|
+
return document.querySelector('[data-pane="conversation"], [class*="centerCol"], .dshDesktopConversationSurface')
|
|
2261
|
+
}
|
|
2217
2262
|
function openGitPage(t) {
|
|
2218
2263
|
if (gitPageHost) { closeGitPage(); return }
|
|
2219
2264
|
const el = document.createElement('div')
|
|
2220
|
-
|
|
2265
|
+
el.setAttribute('data-dsh-git-view', '')
|
|
2266
|
+
el.className = 'dsh-git-view'
|
|
2267
|
+
const column = gitConversationColumn()
|
|
2268
|
+
if (column) {
|
|
2269
|
+
column.appendChild(el)
|
|
2270
|
+
} else {
|
|
2271
|
+
el.setAttribute('data-dsh-git-fallback', '')
|
|
2272
|
+
document.body.appendChild(el)
|
|
2273
|
+
}
|
|
2221
2274
|
const root = require('react-dom/client').createRoot(el)
|
|
2222
2275
|
gitPageHost = { el, root }
|
|
2223
2276
|
root.render(h(GitPage, { onClose: closeGitPage, t }))
|
|
@@ -2269,6 +2322,7 @@ window.__ModuleLoader__.load({
|
|
|
2269
2322
|
.dsh-git-entry .dsh-git-entry-stats{margin-left:auto;display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--dsw-alias-label-secondary,var(--dsw-text-secondary,gray));font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
2270
2323
|
.dsh-git-entry .dsh-git-entry-dot{width:6px;height:6px;border-radius:6px;background:var(--dsw-alias-state-success-primary);display:inline-block}
|
|
2271
2324
|
[data-sidebar-collapsed] .dsh-git-entry,[class*="_collapsed"] .dsh-git-entry{width:36px;height:36px;min-width:36px;margin:0 0 12px;padding:0;justify-content:center;gap:0;text-align:center}
|
|
2325
|
+
html[data-dsh-git-active] .dsh-git-entry{background:var(--dsw-active,rgba(128,128,128,.18));color:var(--dsw-text-primary,inherit);font-weight:500}
|
|
2272
2326
|
[data-sidebar-collapsed] .dsh-git-entry .dsh-git-entry-label,[data-sidebar-collapsed] .dsh-git-entry .dsh-git-entry-stats,[class*="_collapsed"] .dsh-git-entry .dsh-git-entry-label,[class*="_collapsed"] .dsh-git-entry .dsh-git-entry-stats{display:none}
|
|
2273
2327
|
`
|
|
2274
2328
|
document.head.appendChild(style)
|
|
@@ -2368,7 +2422,7 @@ window.__ModuleLoader__.load({
|
|
|
2368
2422
|
ensureStyles()
|
|
2369
2423
|
let t = opts.t || ((key) => ZH[key] ?? EN[key] ?? key)
|
|
2370
2424
|
const root = require('react-dom/client').createRoot(container)
|
|
2371
|
-
root.render(h(GitPage, { onClose: () => {}, t }))
|
|
2425
|
+
root.render(h(GitPage, { onClose: () => {}, t, standalone: true }))
|
|
2372
2426
|
return root
|
|
2373
2427
|
},
|
|
2374
2428
|
apply(ctx) {
|
package/client/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @weibaohui/dsh-git-server — Browser half(原生 dsh 插件 UI,无 iframe/桥接)。
|
|
3
3
|
*
|
|
4
4
|
* 两个面:
|
|
5
|
-
* 1. sidebar.footer.action「Git」→
|
|
6
|
-
* 仓库浏览:文件树/文件内容/提交/分支/工单[列表/新建/评论/开关])。
|
|
5
|
+
* 1. sidebar.footer.action「Git」→ 中栏接管管理页(taskboard 同款,盖会话列不盖侧栏;
|
|
6
|
+
* 仓库列表/建仓/删除 + 仓库浏览:文件树/文件内容/提交/分支/工单[列表/新建/评论/开关])。
|
|
7
7
|
* 2. settings.section「Git 服务器」→ 服务配置(启停/端口/数据目录/兜底密码)。
|
|
8
8
|
*
|
|
9
9
|
* 数据通道:宿主同源路由 /dsh-git-server/api/*(dsh 登录门禁保护,
|
|
@@ -27,6 +27,12 @@ const { createElement: h, useState, useEffect, useCallback } = __React
|
|
|
27
27
|
// ── locale ────────────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
29
|
const NS = 'dshGitServer'
|
|
30
|
+
// 中栏接管(taskboard 同款)开合属性 + 互斥协议
|
|
31
|
+
const DGS_ACTIVE_ATTR = 'data-dsh-git-active'
|
|
32
|
+
const DGS_PANEL_NAME = 'dsh-git'
|
|
33
|
+
const DGS_ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
34
|
+
const DGS_OTHER_ACTIVE_ATTRS = ['data-dsh-atb-active', 'data-dsh-taskboard-active', 'data-dsh-ssh-active', 'data-dsh-prc-active', 'data-dsh-kb-active']
|
|
35
|
+
const DGS_SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="newSession"]'
|
|
30
36
|
const ZH = {
|
|
31
37
|
nav: 'Git', title: '代码仓库',
|
|
32
38
|
myRepos: '我的仓库', newRepo: '新建仓库', repoName: '仓库名', private: '私有',
|
|
@@ -84,7 +90,16 @@ function ensureStyles() {
|
|
|
84
90
|
holder.id = 'dgs-styles'
|
|
85
91
|
holder.style.display = 'none'
|
|
86
92
|
holder.innerHTML = `<style>
|
|
87
|
-
|
|
93
|
+
/* 中栏接管视图(taskboard 同款):容器挂进会话列末尾,html 属性驱动开合,只隐藏列内兄弟。
|
|
94
|
+
* 三代壳层选择器:dev shell data-pane / 官方 CSS-Module centerCol / Desktop 扩展框。 */
|
|
95
|
+
.dsh-git-view{display:none}
|
|
96
|
+
html[data-dsh-git-active] [data-pane="conversation"] > *:not([data-dsh-git-view]),
|
|
97
|
+
html[data-dsh-git-active] [class*="centerCol"] > *:not([data-dsh-git-view]),
|
|
98
|
+
html[data-dsh-git-active] .dshDesktopConversationSurface > *:not([data-dsh-git-view]){display:none !important}
|
|
99
|
+
html[data-dsh-git-active] .dsh-git-view{display:flex;flex-direction:column;height:100%;overflow:hidden}
|
|
100
|
+
/* 壳层退化(会话列缺席):容器兜底挂 body 时退回全屏浮层,保证入口点击永远有响应 */
|
|
101
|
+
.dsh-git-view[data-dsh-git-fallback]{position:fixed;inset:0;z-index:2147483000;display:flex}
|
|
102
|
+
.dgs-page{position:relative;flex:1;min-height:0;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);font-size:14px}
|
|
88
103
|
.dgs-head{display:flex;align-items:center;gap:12px;padding:12px 28px;border-bottom:1px solid var(--dsw-alias-border-l2);flex:none}
|
|
89
104
|
.dgs-h1{font-size:16px;font-weight:700;margin:0}
|
|
90
105
|
.dgs-close{margin-left:auto;cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font-size:18px;padding:4px 8px;border-radius:8px}
|
|
@@ -2040,7 +2055,7 @@ function Admin({ t }) {
|
|
|
2040
2055
|
|
|
2041
2056
|
// ── 主页(仓库列表 + 建仓) ────────────────────────────────────────────────
|
|
2042
2057
|
|
|
2043
|
-
function GitPage({ onClose, t }) {
|
|
2058
|
+
function GitPage({ onClose, t, standalone }) {
|
|
2044
2059
|
useEffect(ensureStyles, [])
|
|
2045
2060
|
const route = useHashRoute()
|
|
2046
2061
|
const [me, setMe] = useState(null)
|
|
@@ -2059,6 +2074,33 @@ function GitPage({ onClose, t }) {
|
|
|
2059
2074
|
window.addEventListener('keydown', onKey)
|
|
2060
2075
|
return () => window.removeEventListener('keydown', onKey)
|
|
2061
2076
|
}, [onClose])
|
|
2077
|
+
// 中栏接管开合:挂载即打开 → html[data-dsh-git-active] + 广播互斥事件;卸载(关闭)时清理。
|
|
2078
|
+
// standalone(__boot 开发挂载)不动全局。
|
|
2079
|
+
useEffect(() => {
|
|
2080
|
+
if (standalone) return undefined
|
|
2081
|
+
for (const attr of DGS_OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
|
|
2082
|
+
document.documentElement.setAttribute(DGS_ACTIVE_ATTR, '')
|
|
2083
|
+
document.dispatchEvent(new CustomEvent(DGS_ACTIVATE_EVENT, { detail: DGS_PANEL_NAME }))
|
|
2084
|
+
return () => { document.documentElement.removeAttribute(DGS_ACTIVE_ATTR) }
|
|
2085
|
+
}, [standalone])
|
|
2086
|
+
useEffect(() => {
|
|
2087
|
+
if (standalone) return undefined
|
|
2088
|
+
const onOther = (e) => { if (e && e.detail !== DGS_PANEL_NAME) onClose() }
|
|
2089
|
+
document.addEventListener(DGS_ACTIVATE_EVENT, onOther)
|
|
2090
|
+
return () => document.removeEventListener(DGS_ACTIVATE_EVENT, onOther)
|
|
2091
|
+
}, [standalone, onClose])
|
|
2092
|
+
// 点侧栏会话行自动关面板(自家入口子树豁免)
|
|
2093
|
+
useEffect(() => {
|
|
2094
|
+
if (standalone) return undefined
|
|
2095
|
+
const onClickRow = (e) => {
|
|
2096
|
+
const target = e.target
|
|
2097
|
+
if (!(target instanceof Element)) return
|
|
2098
|
+
if (target.closest('[' + GIT_ENTRY_ATTR + ']')) return
|
|
2099
|
+
if (target.closest(DGS_SIDEBAR_ROW_SELECTOR)) onClose()
|
|
2100
|
+
}
|
|
2101
|
+
document.addEventListener('click', onClickRow, true)
|
|
2102
|
+
return () => document.removeEventListener('click', onClickRow, true)
|
|
2103
|
+
}, [standalone, onClose])
|
|
2062
2104
|
const notify = (m) => { setToast(m); setTimeout(() => setToast(''), 1600) }
|
|
2063
2105
|
const seg = route.split('/').filter(Boolean)
|
|
2064
2106
|
const view = seg[0] || 'repos'
|
|
@@ -2204,10 +2246,21 @@ function closeGitPage() {
|
|
|
2204
2246
|
try { gitPageHost.el.remove() } catch {}
|
|
2205
2247
|
gitPageHost = null
|
|
2206
2248
|
}
|
|
2249
|
+
function gitConversationColumn() {
|
|
2250
|
+
return document.querySelector('[data-pane="conversation"], [class*="centerCol"], .dshDesktopConversationSurface')
|
|
2251
|
+
}
|
|
2207
2252
|
function openGitPage(t) {
|
|
2208
2253
|
if (gitPageHost) { closeGitPage(); return }
|
|
2209
2254
|
const el = document.createElement('div')
|
|
2210
|
-
|
|
2255
|
+
el.setAttribute('data-dsh-git-view', '')
|
|
2256
|
+
el.className = 'dsh-git-view'
|
|
2257
|
+
const column = gitConversationColumn()
|
|
2258
|
+
if (column) {
|
|
2259
|
+
column.appendChild(el)
|
|
2260
|
+
} else {
|
|
2261
|
+
el.setAttribute('data-dsh-git-fallback', '')
|
|
2262
|
+
document.body.appendChild(el)
|
|
2263
|
+
}
|
|
2211
2264
|
const root = require('react-dom/client').createRoot(el)
|
|
2212
2265
|
gitPageHost = { el, root }
|
|
2213
2266
|
root.render(h(GitPage, { onClose: closeGitPage, t }))
|
|
@@ -2259,6 +2312,7 @@ function mountGitSidebarEntry(t) {
|
|
|
2259
2312
|
.dsh-git-entry .dsh-git-entry-stats{margin-left:auto;display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--dsw-alias-label-secondary,var(--dsw-text-secondary,gray));font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
2260
2313
|
.dsh-git-entry .dsh-git-entry-dot{width:6px;height:6px;border-radius:6px;background:var(--dsw-alias-state-success-primary);display:inline-block}
|
|
2261
2314
|
[data-sidebar-collapsed] .dsh-git-entry,[class*="_collapsed"] .dsh-git-entry{width:36px;height:36px;min-width:36px;margin:0 0 12px;padding:0;justify-content:center;gap:0;text-align:center}
|
|
2315
|
+
html[data-dsh-git-active] .dsh-git-entry{background:var(--dsw-active,rgba(128,128,128,.18));color:var(--dsw-text-primary,inherit);font-weight:500}
|
|
2262
2316
|
[data-sidebar-collapsed] .dsh-git-entry .dsh-git-entry-label,[data-sidebar-collapsed] .dsh-git-entry .dsh-git-entry-stats,[class*="_collapsed"] .dsh-git-entry .dsh-git-entry-label,[class*="_collapsed"] .dsh-git-entry .dsh-git-entry-stats{display:none}
|
|
2263
2317
|
`
|
|
2264
2318
|
document.head.appendChild(style)
|
|
@@ -2358,7 +2412,7 @@ module.exports = {
|
|
|
2358
2412
|
ensureStyles()
|
|
2359
2413
|
let t = opts.t || ((key) => ZH[key] ?? EN[key] ?? key)
|
|
2360
2414
|
const root = require('react-dom/client').createRoot(container)
|
|
2361
|
-
root.render(h(GitPage, { onClose: () => {}, t }))
|
|
2415
|
+
root.render(h(GitPage, { onClose: () => {}, t, standalone: true }))
|
|
2362
2416
|
return root
|
|
2363
2417
|
},
|
|
2364
2418
|
apply(ctx) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weibaohui/dsh-git-server",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "dsh 插件 · Git
|
|
3
|
+
"version": "0.1.4",
|
|
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.
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
51
|
+
try { return require('@deepseek-ai/schemastery') } catch {}
|
|
52
|
+
return null
|
|
69
53
|
}
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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(
|
|
87
|
-
if (!
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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 (
|
|
148
|
+
if (ctx.settings && typeof ctx.settings.update === 'function') {
|
|
159
149
|
try {
|
|
160
|
-
await
|
|
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 (
|
|
257
|
-
try { await
|
|
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
|
-
|
|
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
|
-
|
|
390
|
-
if (
|
|
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:
|
|
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 (
|
|
661
|
+
if (ctx.settings && typeof ctx.settings.update === 'function') {
|
|
658
662
|
try {
|
|
659
|
-
await
|
|
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
|
}
|