feat: Build pipeline — .ts source → .esm.js + .yaml + .d.ts 三件套 #7

Closed
opened 2026-05-06 06:16:14 +00:00 by xiaoju · 1 comment
Owner

背景

当前 uncaged-workflow add 只接受已构建的 .esm.js 文件。用户需要自己用 bundler 构建、手写 descriptor YAML、手写 .d.ts

应该支持直接从 .ts 源文件一步构建出三件套并注册。

设计

add 命令增强

# .ts 源文件 → 自动构建三件套
uncaged-workflow add solve-issue workflows/solve-issue.ts

# .esm.js 产物 → 要求同目录有 .yaml + .d.ts
uncaged-workflow add solve-issue dist/solve-issue.esm.js

检测到 .ts 走 build pipeline,.esm.js 走现有 validate + register。

源文件约定

// workflows/solve-issue.ts
import { createRoleModerator, END, type Role } from "@uncaged/workflow";

// RoleMeta 类型 — build 时提取生成 .d.ts
type Roles = {
  planner: { plan: string; files: string[] };
  coder: { diff: string };
};

// descriptor — build 时提取生成 .yaml
export const descriptor = {
  description: "Solve issues automatically",
  roles: {
    planner: { description: "Creates a plan", schema: { type: "object", properties: { plan: { type: "string" }, files: { type: "array", items: { type: "string" } } } } },
    coder: { description: "Implements the fix", schema: { type: "object", properties: { diff: { type: "string" } } } },
  },
};

// default export — WorkflowFn (AsyncGenerator)
const planner: Role<Roles["planner"]> = async (ctx) => ({ content: "...", meta: { plan: "...", files: [] } });
const coder: Role<Roles["coder"]> = async (ctx) => ({ content: "...", meta: { diff: "..." } });

export default createRoleModerator<Roles>({
  roles: { planner, coder },
  moderator(ctx) { return ctx.steps.length === 0 ? "planner" : END; },
});

Build Pipeline(.ts 输入时)

  1. Bundle: bun build entry.ts --target=node --format=esm --external 'node:*' → 单文件 .esm.js
  2. Descriptor: 动态 import 源文件,读 export const descriptor → 写 .yaml
  3. Types: 从源文件提取 Roles type alias → 生成 .d.ts(具体方案见下文)
  4. Validate: validateWorkflowBundle 验证产物
  5. Hash: XXH64 → Crockford Base32
  6. Register: 三件套写入 bundles/{hash}.esm.js + .yaml + .d.ts,更新 workflow.yaml

.esm.js 输入时

  • 要求同目录存在同名 .yaml.d.ts(或 --descriptor / --types 参数指定)
  • 如果缺 .yaml → 报错
  • 如果缺 .d.ts → warning 但允许(.d.ts 可选)

.d.ts 生成方案

两个可选方案:

方案 A: TypeScript Compiler API

  • ts.createProgram 解析源文件,找到导出给 createRoleModerator 的泛型参数
  • 提取类型定义,生成 .d.ts
  • 优点:精确。缺点:依赖 typescript 包,复杂

方案 B: 从 descriptor JSON Schema 派生

  • descriptor 里已经有每个 role 的 JSON Schema
  • JSON Schema → TypeScript type 是成熟的转换
  • 优点:简单,descriptor 已有。缺点:需要 descriptor 的 schema 足够详细

建议先用方案 B,未来需要时升级到 A。

产出

~/.uncaged/workflow/bundles/
├── C9NMV6V2TQT81.esm.js    # 可执行 bundle
├── C9NMV6V2TQT81.yaml      # descriptor
└── C9NMV6V2TQT81.d.ts      # RoleMeta 类型定义

实现任务

  • packages/workflow/src/build-pipeline.ts — build pipeline 核心逻辑
  • packages/workflow/src/generate-descriptor.ts — descriptor object → YAML string
  • packages/workflow/src/generate-types.ts — JSON Schema → .d.ts(方案 B)
  • 更新 packages/cli-workflow/src/cmd-add.ts — 检测 .ts 走 build pipeline
  • 更新 packages/cli-workflow/src/bundle-store.ts — 存储三件套
  • 更新 validateWorkflowBundle — 验证 .yaml descriptor 格式
  • 写一个示例 workflow .ts 源文件作为集成测试
  • 测试:.ts build → 三件套生成 → add 注册 → run 执行

验证标准

  • uncaged-workflow add test workflows/example.ts 成功生成三件套
  • bundles/ 下有 {hash}.esm.js + {hash}.yaml + {hash}.d.ts
  • uncaged-workflow add test dist/example.esm.js 仍正常工作(向后兼容)
  • 缺 .yaml 时报错
  • bun test 全部通过
  • bunx biome check . 无错误

Ref: RFC-001 Section 2

## 背景 当前 `uncaged-workflow add` 只接受已构建的 `.esm.js` 文件。用户需要自己用 bundler 构建、手写 descriptor YAML、手写 `.d.ts`。 应该支持直接从 `.ts` 源文件一步构建出三件套并注册。 ## 设计 ### `add` 命令增强 ```bash # .ts 源文件 → 自动构建三件套 uncaged-workflow add solve-issue workflows/solve-issue.ts # .esm.js 产物 → 要求同目录有 .yaml + .d.ts uncaged-workflow add solve-issue dist/solve-issue.esm.js ``` 检测到 `.ts` 走 build pipeline,`.esm.js` 走现有 validate + register。 ### 源文件约定 ```typescript // workflows/solve-issue.ts import { createRoleModerator, END, type Role } from "@uncaged/workflow"; // RoleMeta 类型 — build 时提取生成 .d.ts type Roles = { planner: { plan: string; files: string[] }; coder: { diff: string }; }; // descriptor — build 时提取生成 .yaml export const descriptor = { description: "Solve issues automatically", roles: { planner: { description: "Creates a plan", schema: { type: "object", properties: { plan: { type: "string" }, files: { type: "array", items: { type: "string" } } } } }, coder: { description: "Implements the fix", schema: { type: "object", properties: { diff: { type: "string" } } } }, }, }; // default export — WorkflowFn (AsyncGenerator) const planner: Role<Roles["planner"]> = async (ctx) => ({ content: "...", meta: { plan: "...", files: [] } }); const coder: Role<Roles["coder"]> = async (ctx) => ({ content: "...", meta: { diff: "..." } }); export default createRoleModerator<Roles>({ roles: { planner, coder }, moderator(ctx) { return ctx.steps.length === 0 ? "planner" : END; }, }); ``` ### Build Pipeline(`.ts` 输入时) 1. **Bundle**: `bun build entry.ts --target=node --format=esm --external 'node:*'` → 单文件 `.esm.js` 2. **Descriptor**: 动态 import 源文件,读 `export const descriptor` → 写 `.yaml` 3. **Types**: 从源文件提取 `Roles` type alias → 生成 `.d.ts`(具体方案见下文) 4. **Validate**: `validateWorkflowBundle` 验证产物 5. **Hash**: XXH64 → Crockford Base32 6. **Register**: 三件套写入 `bundles/{hash}.esm.js` + `.yaml` + `.d.ts`,更新 `workflow.yaml` ### `.esm.js` 输入时 - 要求同目录存在同名 `.yaml` 和 `.d.ts`(或 `--descriptor` / `--types` 参数指定) - 如果缺 `.yaml` → 报错 - 如果缺 `.d.ts` → warning 但允许(.d.ts 可选) ### `.d.ts` 生成方案 两个可选方案: **方案 A: TypeScript Compiler API** - 用 `ts.createProgram` 解析源文件,找到导出给 `createRoleModerator` 的泛型参数 - 提取类型定义,生成 `.d.ts` - 优点:精确。缺点:依赖 typescript 包,复杂 **方案 B: 从 descriptor JSON Schema 派生** - descriptor 里已经有每个 role 的 JSON Schema - JSON Schema → TypeScript type 是成熟的转换 - 优点:简单,descriptor 已有。缺点:需要 descriptor 的 schema 足够详细 建议先用方案 B,未来需要时升级到 A。 ### 产出 ``` ~/.uncaged/workflow/bundles/ ├── C9NMV6V2TQT81.esm.js # 可执行 bundle ├── C9NMV6V2TQT81.yaml # descriptor └── C9NMV6V2TQT81.d.ts # RoleMeta 类型定义 ``` ## 实现任务 - [ ] `packages/workflow/src/build-pipeline.ts` — build pipeline 核心逻辑 - [ ] `packages/workflow/src/generate-descriptor.ts` — descriptor object → YAML string - [ ] `packages/workflow/src/generate-types.ts` — JSON Schema → .d.ts(方案 B) - [ ] 更新 `packages/cli-workflow/src/cmd-add.ts` — 检测 .ts 走 build pipeline - [ ] 更新 `packages/cli-workflow/src/bundle-store.ts` — 存储三件套 - [ ] 更新 `validateWorkflowBundle` — 验证 .yaml descriptor 格式 - [ ] 写一个示例 workflow `.ts` 源文件作为集成测试 - [ ] 测试:.ts build → 三件套生成 → add 注册 → run 执行 ## 验证标准 - [ ] `uncaged-workflow add test workflows/example.ts` 成功生成三件套 - [ ] bundles/ 下有 `{hash}.esm.js` + `{hash}.yaml` + `{hash}.d.ts` - [ ] `uncaged-workflow add test dist/example.esm.js` 仍正常工作(向后兼容) - [ ] 缺 .yaml 时报错 - [ ] `bun test` 全部通过 - [ ] `bunx biome check .` 无错误 Ref: RFC-001 Section 2
Author
Owner

验证结果

  • uncaged-workflow add hello-world examples/hello-world.ts → 三件套生成
    • 5RGH4Y79PZAXY.esm.js (465KB, 所有依赖 inline)
    • 5RGH4Y79PZAXY.yaml (descriptor)
    • 5RGH4Y79PZAXY.d.ts (Roles 类型定义)
  • .yaml 内容正确:description + roles + JSON Schema
  • .d.ts 内容正确:从 JSON Schema 派生的 TypeScript 类型
  • bun test — 63 tests, 0 fail
  • bunx biome check . — 76 files, no errors

小橘 🍊(NEKO Team)

## 验证结果 ✅ - ✅ `uncaged-workflow add hello-world examples/hello-world.ts` → 三件套生成 - `5RGH4Y79PZAXY.esm.js` (465KB, 所有依赖 inline) - `5RGH4Y79PZAXY.yaml` (descriptor) - `5RGH4Y79PZAXY.d.ts` (Roles 类型定义) - ✅ .yaml 内容正确:description + roles + JSON Schema - ✅ .d.ts 内容正确:从 JSON Schema 派生的 TypeScript 类型 - ✅ `bun test` — 63 tests, 0 fail - ✅ `bunx biome check .` — 76 files, no errors 小橘 🍊(NEKO Team)
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#7