Files
gitea/gitea-publisher/README.md
T

8.3 KiB
Raw Blame History

@local/gitea-publisher

DeepSeek Harness 插件:在会话顶栏加一个「Gitea」按钮,把当前会话工作区的 最终项目源码做成一次快照提交,push 到本地部署的 Gitea。 一个工作区对应一个独立仓库:不同工作区各推各的仓库,互不混库。

工作方式

  • 宿主半边(index.js):giteaPublish 服务,导出 status / publish 两个 Remote 方法 (typert SRC 回退模式,零依赖,只用 node: 内置模块 + 系统 git)。
  • 浏览器半边(client.js):注册进 conversation.session.header.actions 槽位, 用 ctx.remote.$mount 挂载描述符;点按钮 → 宿主执行快照并推送。
  • 一个工作区一个仓库:目标仓库名按工作区目录 basename 派生(非法字符换 -,截 60 字符); 目录名已被别的工作区绑定、或在 Gitea 上已存在(不许悄悄复用未知仓库)时,自动加 -<工作区路径sha1前8位> 后缀区分。首次发布成功后才写入绑定表 ~/.dsh/gitea-publisher-map.json(原子写;推失败不留脏绑定),此后该工作区永远推绑定到的仓库, 改名重绑用面板请求里的 repo 字段(也持久进绑定表)。repoFromWorkspace: false 退回旧行为(所有会话共用配置里的固定 repo)。
  • 发布流程:GET /user 验证凭证 → 仓库不存在时自动建仓(owner 是组织就走 POST /orgs/{owner}/repos,个人走 POST /user/repos;可见性由 private 控制, 默认 false=完全公开)→ 工作区复制到临时目录(跳过 .git/node_modules/dist 等垃圾目录与超限大文件, .gitignore 在 git add 阶段自然生效)→ git init → 先 fetch 远端目标分支并基于它提交(线性历史、永远可快进;内容没变就不产生新提交, 返回 unchanged: true 幂等成功)→ git push(凭证走 URL 内嵌 basic auth, 对外报错文本会抹掉 token 及其 URL 转义形态)。
  • force: true 改变语义:跳过 fetch,直接推「单提交快照历史」并 --force 覆盖远端分支。
  • 认证两种模式:username + token(=密码) 走 HTTP Basic(适配没开放令牌 API 的实例); username 留空则按 token <PAT> 头。
  • 按钮弹层显示目标仓库、发布进度、成功(commit/文件数/新建或无变更标注/仓库链接)或失败原因。

配置

写在 profile 的 ~/.dsh/profiles/web/cordis.patch.yml(gitea-publisher 行的 config.gitea;bundle 自带的 cordis.patch.yml 只是默认值副本)。本机已接入:

config:
  gitea:
    baseUrl: "http://10.0.0.131:3000"   # 不带结尾斜杠
    username: "dsh"                     # 填了就走 Basic 认证(账号密码)
    token: "<账号密码或个人访问令牌>"      # 该实例未开放令牌 API,用账号密码
    owner: "XH"                         # 组织 XH:自动建仓建到组织下(个人用户名亦可)
    branch: "main"
    createIfMissing: true
    private: false                      # 自动创建时可见性:false=完全公开(默认)
    force: false
    maxFileBytes: 31457280
    # repoFromWorkspace: true           # 默认开:一工作区一仓库;false = 旧的固定单仓库模式
    # repo: "xxx"                       # 仅固定模式使用;per-workspace 模式下忽略
    # mapFile: "~/.dsh/gitea-publisher-map.json"   # 工作区→仓库 绑定表位置

注意:密码明文存在 profile 配置里,这台机器上等价于「本机可读」,介意的话 在 Gitea 侧给 dsh 单独建低权限账号或恢复令牌 API 后换 PAT。

本机安装与热更新(实测结论)

这台机器 PATH 上没有 pnpm,手工接线:

  1. ~/.dsh/profiles/web/node_modules/@local/gitea-publisher/ 是一个 dev-link 中继目录: index.js 是入口中继(每次 apply 带一次性 query 重新 import 工作区真实代码,绕开 ESM 模块缓存;.heartbeat.json 为可观测证据),client.js 是指向工作区的符号链接, package.json 是内容拷贝(若改了包 dsh.* 字段需重新 cp)。
  2. profile cordis.patch.yml 末尾的 - insert: 行(id gitea-publisher)。
  3. profile 装载/配置变更需要重启 dsh-web 才在宿主生效(实测:这台机器的 systemd 单元用裸 node 启动、没有 --expose-internals,dsh-hmr 起不来 → 没有任何 patch/模块文件监视;此前「新行热挂上」的现象只是 Web 服务按请求重扫 client 模块清单, 宿主侧并没有装载)。浏览器端刷新页面后才能看到按钮。 可选改进:给 unit 的 ExecStart 换成 node --expose-internals /usr/local/bin/dsh web … 并重启一次,之后 HMR 链路(改代码热重载)就能用。

真机验证(2026-10-13,test/verify-real.mjs 全绿,每工作区模式)

对真实实例 http://10.0.0.131:3000(账号密码 Basic 认证)用临时合成工作区跑通 (绑定表放隔离临时文件,测试仓库结尾自动删除):

  • status 配置校验 / API 可达(apiUser=dsh)、repoPolicy=per-workspace、未绑定工作区预测派生名
  • 提交:按目录名自动在组织 XH 下建公开仓库(如 XH/dsh-verify-ws)+ 推送(commit/files 回执正常)
  • 独立性:换另一个工作区目录 → 另建 XH/dsh-verify-other,两仓库互不共享;重发命中已绑定仓库(repoSource=bound)
  • (历史)旧固定模式曾用本项目工作区建 XH/gitea-publisher,验证条目同上;切换后本工作区首次点击会改推 XH/gitea
  • 归属与可见性:API 匿名可读、private=false、owner=XH,匿名 git 拉取 200
  • 拉取:clone 回本地,文件树、HEAD 与回执一致
  • 归档:GET /api/v1/repos/XH/gitea-publisher/archive/main.zip 下载校验,zip 内容有效
  • 同内容重发 → unchanged: true 幂等;内容变化重发 → 基于远端线性提交可快进
  • force 重发 → 单提交历史覆盖,远端 fetch 到预期 HEAD
  • 全程返回文本无凭证泄漏(含 URL 百分号转义形态)

另有离线套件:test/e2e-host.mjs(假 Gitea + 真 git http-backend 全链路)、 test/e2e-client.mjs(描述符校验/字典完备性/组件渲染)。

客户端接线的三个坑(真实浏览器里踩过,全是静默故障)

  1. 动态 $mount 的命名空间不能静态 inject。remote.giteaPublish 是本包 apply 里 $mount 之后才出现的,把它写进 exports.inject 会让 fiber 永远等待自己提供 → 整个 客户端 boot 报 pending (waiting for service: remote.giteaPublish)(官方 typed client 能静态注入是因为挂载方是 boot 期的 dsh-api-remotes,不是同一个包)。
  2. ctx.remote.<ns> 属性糖受 inject 守卫,动态挂载后直接取属性会抛 cannot get property "remote.giteaPublish" without inject;官方同款解法是 ctx.get('remote.giteaPublish')(见 validateContribution 自身)。
  3. 命名空间方法的返回值带 {ok, value} RPC 信封(src-json 的宿主返回值在 value 里)。不解包的话 status.configured 恒为 undefined——面板渲染成"目标"标签下空白、 发布按钮消失的半残状态,且不报任何错。解包在 inject 闭包里做:信封 ok:false 是协议层失败(撤回/断连),宿主业务失败在 value.ok=false。

UI 侧兜底:status 加载失败/20 秒超时都会显示错误文本 + 重试按钮,不再有无限 "正在检查配置…"。宿主重启会让旧页面的连接失效,重启 dsh-web 后浏览器必须刷新一次。

已知边界

  • 按钮推的是当前会话的工作区(lookup 由 sessionId 解析出 workspaceRoot)。
  • 每工作区仓库的判定发生在每次 status/publish 时:绑定表被手工删掉后,若派生名对应仓库 已在远端存在,会被视为「占用」而加后缀,不会重新认领旧名(防止两个工作区抢同一仓库)。
  • 工作区目录改名 = 换仓库(绑定按工作区路径记);换机器/换路径同理。
  • force: true 会用单提交快照覆盖远端分支历史,确认后再开。
  • 会话工作区里若已有 .git,其历史不会被带进快照(只发源码现状)。
  • 大文件阈值 maxFileBytes 之外的文件跳过并在结果里报告数量。