Skip to content

Patch 编写指南

Patch Provider 是一个普通 DSH 插件,通过 package.json 声明一个或多个 CommonJS 模块。Harmony 会在目标插件执行前,从当前 Loader profile 发现这些模块。

Provider 声明

json
{
  "name": "my-dsh-plugin",
  "dsh": {
    "harmony": {
      "patches": ["./patches/answer.patch.cjs"],
      "after": ["base-patches"],
      "before": ["ui-patches"],
      "conflicts": ["legacy-patches"]
    }
  }
}

Patch 文件必须使用 CommonJS,以便实时 Loader 更新时同步收集。

如果 Provider 本身依赖 Harmony 服务,使用 DSH 已有依赖机制:

ts
export const inject = ['harmony']

或在 Loader 行声明:

yaml
- id: my-plugin
  inject: [harmony]

源码 Patch

源码 Patch 选择 TypeScript AST 节点,并通过 MagicString 编辑当前源码:

js
/** @type {import('dsh-harmony').HarmonyPatch} */
module.exports = {
  id: 'answer-value',
  target: {
    package: 'some-dsh-plugin',
    version: '^1.2.0',
    files: ['lib/index.js'],
  },
  select: 'FunctionDeclaration[name.name="answer"] NumericLiteral',
  expect: 1,
  apply({ node, sourceFile, edit }) {
    edit.overwrite(node.getStart(sourceFile), node.getEnd(), '42')
  },
}

select 使用 TSQuery。回调参数包括:

字段内容
patch稳定键与 Provider Owner
source所有早期 Patch 产生的源码
sourceFileTypeScript AST
node当前匹配节点
edit针对当前源码的 MagicString 编辑器
tsTypeScript 命名空间

传给 edit 的位置都以当前 Patch 收到的源码为准。files 是备选包内相对路径,Harmony 使用第一个存在的文件;version 是 SemVer 范围;expect 要求精确匹配数。

语义 Patch

具名函数声明和类方法可以直接装饰,无需手写 AST 编辑:

js
module.exports = {
  id: 'answer-after',
  target: {
    package: 'some-dsh-plugin',
    version: '^1.2.0',
    files: ['lib/index.js'],
    function: 'answer',
  },
  operation: 'after',
  handler({ result }) {
    return result + 1
  },
}
操作行为
before目标执行前运行,可返回替换参数数组
after目标执行后运行,可替换同步或异步结果
around通过 invoke(args?) 控制下一层是否及如何执行
replace通过 invoke(args?) 替换目标;同一函数只能有一个启用的 Replace

所有 before 按 Patch 顺序执行;aroundreplace 按顺序形成由外到内的链;所有 after 再按 Patch 顺序执行。源码 Patch 与语义 Patch 共享同一全局 Provider 顺序。

语义目标当前要求具名参数,不支持 Generator。处理器在 Node.js 中执行,因此 lib/client.js 等浏览器目标必须使用源码 Patch。

顺序约束

beforeafter 指向 Provider 包名,是排序约束而非 npm 或 Cordis 依赖。手动列表始终有效;自动排序只寻找违反约束最少的顺序。

同一 Provider 的 Patch 按声明顺序运行;Provider 按 profile 顺序运行;后续源码 Patch 会收到早期 Patch 的输出。

Provider 冲突

conflicts 声明 Provider 不兼容关系,单侧声明即可。只有当前 Loader Tree 中两者都是启用状态的 Patch Provider 时才显示警告。

该警告不会阻止安装、启动、应用、排序或重载。停用任一 Provider 的 <provider>/* 后警告消失。

最小 WebUI 示例

下面的 Patch 替换会话客户端中编译后的新会话标题:

js
const headline = 'Harmony is All You Need'

module.exports = {
  id: 'home-banner',
  target: {
    package: '@deepseek-ai/dsh-client-ui-conversation',
    version: '0.1.0-rc.6',
    files: ['lib/client.js'],
  },
  select: 'StringLiteral[text="探索未至之境"]',
  expect: 1,
  apply({ node, sourceFile, edit }) {
    edit.overwrite(node.getStart(sourceFile), node.getEnd(), JSON.stringify(headline))
  },
}

Harmony 修改后的 WebUI 新会话主横幅

服务与工具 API

插件可以注入 harmony 服务:

ts
export const inject = ['harmony']

export function apply(ctx) {
  const snapshot = ctx.harmony.inspect({ package: 'some-dsh-plugin' })
}

服务提供 binEntryprofileDirinspect(input?)inspectDependencies(owner)reloadPlugin(name);后者用于以事务方式重载一个 Loader 插件及其 Patch 声明。包还导出扩展发现工具及其 TypeScript 类型。Preview 与 Draft 生命周期 API 属于 WebUI Studio,而不是 Harmony。

基于 MIT License 发布。