关于 encore-migrate
encore-migrate 是将现有后端逐步迁移到 Encore.ts 或 Encore Go 的技能,适用于不同语言和框架的项目。它通过分阶段发现、规划和迁移,并用计划文件记录单元与进度,便于跨会话继续工作。
主要功能
- 01支持多种源语言和框架
- 02按迁移单元分阶段推进
- 03分析后端实体与依赖
- 04生成摘要及单元计划文件
- 05支持按进度恢复迁移
适用场景
- 01盘点现有后端服务与 API
- 02将系统拆分为迁移单元
- 03按依赖顺序迁移后端功能
- 04跨会话追踪迁移进度
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ encoredev/ skills --skill encore-migrate安装后在 Claude Code 中输入/encore-migrate确认已加载。
详细介绍
迁移到 Encore
此技能将指导你逐个迁移单元,将任何现有后端应用迁移到 Encore。它支持任何源语言或框架,目标为 Encore.ts 和 Encore Go。迁移过程中,会在 Encore 项目根目录创建 migration-plan.md 摘要文件和 migration-plan/ 目录,其中包含每个单元的详细文件,以便跨会话跟踪进度。此技能不包含 Encore 代码示例——所有 Encore 特定实现均由相应语言的专用技能负责。
阶段检测
开始前,先确定要进入的阶段:
- Encore 项目目录中不存在
migration-plan.md→ 从阶段 1:发现开始 - 存在
migration-plan.md,但不存在migration-plan/目录 → 从阶段 2:规划继续(发现已完成,但尚未编写详细文件) migration-plan/目录存在未完成的单元(摘要中任一单元的状态为pending或in progress)→ 从阶段 3:迁移继续- 摘要中所有单元的状态均为
migrated、skipped或manual validation needed→ 进入阶段 4:完成
自主模式
此工作流中的确认暂停点(阶段 1 确认单元、阶段 2 批准第一个单元、阶段 3 选择每个后续单元)为默认行为。如果用户要求不间断地完成迁移(“直接迁移”“全部处理”“别问了”),则跳过这些暂停点:
- 对未定事项采用合理的默认选项(参见阶段 1 中的“关键决策”),并在编写代码前用简短列表告知用户每项决定
- 在
migration-plan.md的## Decisions部分记录每个默认选项,以便后续重新审视 - 对源系统发起可能产生副作用的 HTTP 调用前仍须询问,且绝不修改源系统
- 将实体标记为
migrated前仍须逐一验证
恢复迁移(阶段 3)
当 migration-plan.md 和 migration-plan/ 均存在且有未完成单元时:
- 阅读
migration-plan.md(仅摘要——不要读取所有详细文件) - 向用户报告当前状态——例如:“7 个单元中已有 3 个完成迁移,下一个建议处理:billing(它的所有依赖项均已迁移)”
- 询问用户接下来想处理什么,并根据计划中的依赖顺序提供建议
阶段 1 — 发现
1. 收集信息
向用户询问:
- 源系统路径(要迁移的现有代码库)
- 源系统运行时的本地 URL(如适用——后续进行 HTTP 对比验证时需要)
- 目标语言: Encore.ts 或 Encore Go
2. 分析源代码库
阅读源代码库并清点所有实体:
| 类别 | 查找内容 |
|---|---|
| 服务 / 模块 / 领域 | 不同的有界上下文、可独立部署的单元、路由分组 |
| API 端点 | 方法、路径、处理函数、请求 / 响应结构 |
| 数据库 | 类型(Postgres、MySQL 等)、表、模式、迁移文件 |
| Pub/Sub 主题和订阅 | 主题名称、发布者、订阅者、消息结构 |
| Cron 作业 / 定时任务 | 计划表达式、处理函数 |
| Auth 中间件 / 处理程序 | 身份验证策略、令牌验证、会话管理 |
| 密钥 / 环境变量 | 所有引用的环境变量和密钥,并注明哪些是敏感信息 |
| 全局 HTTP 行为 | CORS 设置、全局路径前缀、全局中间件、自定义错误过滤器 / 错误响应正文 |
| 现有测试 | 测试文件、覆盖的实体、所用测试框架 |
| 前端代码 | React/Vue/Angular 组件、静态 HTML、CSS、客户端 JavaScript——这些不在范围内 |
3. 识别前端代码
全栈仓库和单体仓库通常会混合后端和前端代码。迁移仅针对后端,前端代码不在范围内。
检测前端目录并将其标记为不在范围内。 常见指标:
| 模式 | 示例 |
|---|---|
| 专用前端目录 | frontend/、client/、web/、app/(其中包含 React/Vue/Angular 时)、src/components/、public/ |
| 前端配置文件 | next.config.js、vite.config.ts、nuxt.config.ts、angular.json、.svelte-kit/、remix.config.js |
| 软件包依赖项 | package.json 中的 react、vue、@angular/core、svelte |
标记应当迁移的框架服务端代码。 有些前端框架包含后端逻辑,其中包含 API 端点、数据库查询或服务端业务逻辑:
| 框架 | 服务端代码位置 | 查找内容 |
|---|---|---|
| Next.js | pages/api/、app/*/route.ts | API 路由处理程序——这些是后端端点 |
| Remix | app/routes/*.tsx(导出 loader/action) | loader 和 action 函数包含服务端逻辑 |
| Nuxt | server/api/、server/routes/ | 服务端 API 路由 |
| SvelteKit | src/routes/+server.ts、+page.server.ts | 服务端端点和加载函数 |
| Astro | src/pages/*.ts(非 .astro 文件) | API 端点 |
如果发现框架服务端代码,请询问用户如何处理。 并非所有服务端代码都应该迁移到 Encore——有时,精简的后端层(BFF、身份验证代理、SSR 数据获取)应与 Encore 后端一起保留在前端框架中。
向用户说明发现的内容并询问:
“我在你的 应用中发现了 个服务端路由(例如,
pages/api/users.ts、app/billing/route.ts)。这些路由包含可能迁移到 Encore 的后端逻辑,但有些团队更愿意在前端框架中保留精简的服务端层,用于 SSR 数据获取或 BFF 代理等功能。你希望:
- 全部迁移——将所有服务端路由迁移到 Encore
- 迁移部分路由——我会列出这些路由,由你选择要迁移的路由
- 全部保留在 中——仅迁移独立的后端代码”
根据用户的选择:
- 全部迁移: 将后端逻辑提取到迁移单元中。前端渲染代码不在范围内。在迁移计划中注明哪些源文件同时包含前端和后端代码。
- 迁移部分路由: 列出服务端路由供用户选择。将选中的路由纳入迁移单元,其余路由标记为不在范围内。
- 全部保留: 将所有框架服务端代码与前端代码一并标记为不在范围内。仅将独立的后端代码(Express 路由、独立 API 服务等)纳入迁移单元。
向用户报告: 列出所有检测到的前端目录,以及对框架服务端代码作出的决定。例如:“我在 app/ 中发现了一个 Next.js 前端——React 组件不在范围内。你选择将 pages/api/ 中 12 个 API 路由里的 8 个迁移到 Encore,并将 4 个精简代理路由保留在 Next.js 中。”
4. 确定关键决策
某些选择会影响每个单元。请在规划前明确这些问题,并将答案记录在 migration-plan.md 的 ## Decisions 部分:
- 行为目标: 旧代码库往往存在 Bug(缺少所有权检查、字段从未保存、筛选条件错误)。询问用户,是要完全复现源代码的行为,还是修复明显的 Bug。如果源代码实现了已发布的 API 契约(OpenAPI 规范、RealWorld、合作伙伴集成),通常应按该契约进行修复。无论选择哪种方案,都要在单元的 Notes 列中列出每项有意做出的差异。
- 错误响应格式: Encore 的类型化
api()端点始终以{code, message, details}的格式返回错误,且请求验证失败时始终返回 HTTP 400。如果客户端依赖不同的错误响应正文(例如{"errors": {...}})或状态码(例如 422),有两种选择:接受 Encore 的格式(字段错误可放在details中),或对受影响的路由使用api.raw,但这样就无法使用类型化请求和生成的客户端。询问用户倾向哪种方案。 - CORS: 如果源代码启用了 CORS,请将其转换为
encore.app中的global_cors,而不要依赖 Encore 的默认设置。
5. 将实体分组为迁移单元
使用以下优先级启发式方法,将发现的实体分组为迁移单元:
- 现有服务边界 — 如果源应用已有服务、模块或包,请以此作为划分单元的起点
- URL 路径前缀 — 将共享路径前缀的端点分组(例如
/users/*、/billing/*) - 共享数据库表 — 读写相同表的端点应归为一组
- 共享类型/模型 — 共享请求/响应类型或领域模型的端点应归为一组
分组规模: 每个迁移单元以包含 5–15 个端点为宜。如果某组超过约 15 个端点,建议进一步拆分(例如 users-crud 和 users-admin)。如果某组少于 3 个端点,可考虑与相关分组合并。
横切关注点应单独划分为迁移单元:身份验证、密钥和独立基础设施(发布/订阅主题、与任何服务都没有紧密耦合的定时任务)都应作为单独的单元,因为它们属于不同的依赖层级。
对于没有明确边界的单体应用: 先按 URL 路径前缀分组,然后询问:“这些分组是按 URL 路径划分的——您希望按领域重新组织吗?”
6. 展示迁移单元
通过摘要表向用户展示迁移单元:
| 单元 | 端点 | 数据库表 | 其他 | 复杂度 |
|---|
列出总数(例如,“7 个迁移单元,涵盖 42 个端点和 3 个数据库”)。评估每个单元的整体迁移复杂度:
- 低 — 有直接对应的 Encore 实现,映射简单
- 中 — 需要重构,或只有部分 Encore 实现可对应
- 高 — 没有直接对应的实现,需要重新设计或自定义解决方案
如果用户想在确认前查看单元包含的内容,可主动提出展示任一单元的详细信息。
7. 展示代码预览
选择 2–3 个有代表性的实体(从不同单元中选取简单和复杂的实体),展示一段简短的“迁移前后”预览,分别呈现源代码当前的样子和 Encore 版本的样子。使用相应的语言专用技能来指导预览。预览应简短——每段预览展示一个端点、一个查询或一个主题声明即可。
8. 请用户确认
请用户确认迁移单元是否正确。具体询问:
- “是否有我遗漏的服务、端点或其他实体?”
- “您希望拆分、合并或重命名其中任何迁移单元吗?”
- “您希望将任何内容排除在迁移之外吗?”
9. 根据需要迭代
如果用户指出有遗漏的实体,或希望调整分组边界,请更新迁移单元并重新展示摘要表。重复此过程,直到用户确认迁移单元准确无误。
阶段 2 — 规划
1. 检查是否已有 Encore 项目
检查目标路径下是否已有 Encore 项目(查找 encore.app 文件)。如果有,请向用户确认这是否是正确的项目。如果没有,通过调用 encore-getting-started 技能(Go 项目则调用 encore-go-getting-started)帮助创建项目。
2. 收集目标信息
向用户询问:
- Encore 项目的路径(迁移后的代码所在位置)
- Encore 应用运行的本地 URL(默认值:
http://localhost:4000)
3. 确定依赖顺序
根据依赖关系排列迁移单元。按以下层级顺序执行:
- 密钥 / 配置 — 无依赖,是其他所有内容所需的
- 数据库 — 服务使用数据库前,必须先准备好架构和迁移
- 身份验证 — 需要先实现身份验证处理程序,再实现受保护的端点
- 叶子单元 — 没有跨服务依赖的单元
- 依赖单元 — 依赖已迁移单元的单元
- Pub/Sub 主题和订阅 — 通常依赖于已就绪的服务
- 定时任务 — 通常依赖于服务端点
在每个层级内,优先建议最简单的单元(端点最少、架构最小、复杂度最低)。
4. 编写 migration-plan.md(摘要)
使用下方“migration-plan.md 格式”部分中的模板,在 Encore 项目根目录编写 migration-plan.md 摘要文件。将所有迁移单元的状态设为 pending。
5. 编写详细文件
在 Encore 项目根目录创建 migration-plan/ 目录。使用下方“详细文件格式”部分中的模板,为每个迁移单元编写一个详细文件。每个文件命名为 migration-plan/<unit-name>.md。
6. 提议第一个单元
提议第一个迁移单元,并根据依赖顺序说明应优先迁移它的原因。等待用户批准后再继续进入阶段 3。
阶段 3 — 迁移(循环)
1. 确定下一个单元
读取 migration-plan.md(仅摘要),并根据依赖顺序确定下一个状态为 pending 的迁移单元。
2. 建议并确认
建议下一个要迁移的单元,并说明选择它的原因(例如:“此单元不依赖任何尚未迁移的单元”或“必须先准备好数据库,才能迁移使用它的服务”)。询问用户是要继续迁移此单元,还是选择其他单元。
3. 加载单元详细信息
读取所选单元的详细文件(migration-plan/<unit-name>.md)。不要读取其他单元的详细文件。
4. 迁移各实体
对于单元中的每个实体:
a. 实现
根据实体类型和目标语言,调用相应的语言专属技能:
| 正在迁移的内容 | Encore.ts 技能 | Encore Go 技能 |
|---|---|---|
| 服务结构 | encore-service | encore-go-service |
| API 端点 | encore-api | encore-go-api |
| 身份验证 | encore-auth | encore-go-auth |
| 数据库 + 迁移 | encore-database | encore-go-database |
| Pub/Sub 主题和订阅 | encore-pubsub | encore-go-pubsub |
| 定时任务 / 计划任务 | encore-cron | encore-go-cron |
| 对象存储 / 文件上传 | encore-bucket | encore-go-bucket |
| 缓存(Redis) | encore-cache | encore-go-cache |
| 密钥 / API 密钥 / 凭据 | encore-secret | encore-go-secret |
| Webhook(Stripe、GitHub 等) | encore-webhook | encore-go-webhook |
| 测试 | encore-testing | encore-go-testing |
b. 迁移测试
如果源实体有关联的测试,使用相应的测试技能(encore-testing 或 encore-go-testing)迁移测试。调整测试断言,使其符合 Encore API 模式。如果源实体没有测试,请在详细文件中注明。
c. 验证
每个实体在标记为 migrated 前,都要经过三个验证层。每个实体都必须完成所有适用层的验证。
第 1 层:测试迁移(主要)
- 迁移实体时,同时迁移其关联的测试
- 使用
encore-testing技能(Go 项目使用encore-go-testing)实现测试 - 运行测试——测试必须通过,才能将实体标记为
migrated - 如果源实体没有测试,请在计划中注明“源代码没有测试”,并依靠其他层进行验证
第 2 层:HTTP 对比(仅限端点,尽力而为)
当两个系统都在本地运行时,分别调用源系统和 Encore 应用中的同一端点,然后进行比较:
- HTTP 状态码——必须一致
- 响应正文结构——键和形状必须一致(时间戳或 ID 等动态数据的值可以不同)
以下情况跳过此层:
- 端点需要 Agent 无法获取的身份验证凭据(询问用户,允许跳过)
如果连接任一系统的请求失败,请让用户启动应用后再重试。不要默默跳过——用户可能只是忘了启动应用。
进行任何可能产生副作用的 HTTP 调用前,始终先询问用户。
第 2 层替代方案:契约测试套件
如果源系统实际上无法运行(数据库引擎不可用、工具链太旧而无法安装、存在外部依赖),或者目标行为有意偏离源系统,则与源系统进行 HTTP 对比没有意义。如果 API 有外部契约测试套件(Hurl、Postman/Newman、Bruno、针对 OpenAPI 规范运行的 Schemathesis,或使用方的集成测试),请改为针对 Encore 应用运行该套件:
- 运行完整套件并继续执行失败项(例如
hurl --test --continue-on-error --report-json),以便显示每项失败断言,而不只是每个文件中的第一项 - 对失败进行分类。区分已记录决策(例如错误响应格式)导致的预期差异与实际行为缺陷,并修复这些缺陷
- 在 Validation Log 中记录通过/失败的数量及分类
在计划中注明跳过与源系统进行 HTTP 对比的原因。
第 3 层:完成前验证关卡
在将任何实体标记为 migrated 之前,Agent 必须有当前会话中的最新证据:
- 显示通过数量和退出代码的测试命令输出,或
- 显示匹配结果的 HTTP 对比结果,或
- 用户明确批准跳过验证
规则:
- 不要使用“应该能用”“看起来正确”或“似乎没问题”之类的说法——只能使用有证据支持的结论
- Agent 必须准确说明验证了什么,以及输出结果是什么
- 如果证据不足,将实体标记为
manual validation needed,而不是migrated - 之前会话的过期证据不算数——恢复工作时重新运行验证
d. 更新详细文件
在单元的详细文件(migration-plan/<unit-name>.md)中更新实体状态,并在该文件的 Validation Log 表格中记录验证证据。
e. 更新摘要
单元中的所有实体都完成后,在 migration-plan.md 中将单元状态更新为 migrated。如果仍有实体待处理,则将单元状态设为 in progress。
5. 继续或暂停
完成一个单元后,询问“你接下来想迁移什么?”,并根据依赖顺序建议下一个单元。
6. 批量处理
默认一次处理一个单元。如果用户说“继续”“全部处理”或类似的话,可以批量处理多个单元,但仍须在将每个实体标记为已迁移之前分别进行验证。
阶段 4 — 完成
当 migration-plan.md 中的所有单元都标记为 migrated、skipped 或 manual validation needed 时:
- 根据
migration-plan.md提供最终摘要:- 已迁移的单元总数
- 标记为
manual validation needed的单元——阅读对应的详细文件,并列出需要关注的实体及原因 - 已跳过的单元(列出单元及原因)
- 建议最后再运行一次完整测试套件,以发现任何集成问题
- 说明仍需人工关注的手动验证事项
- 如果源系统包含前端代码,建议使用
encore-frontend技能将前端重新连接到新的 Encore 后端(生成类型化 API 客户端、配置 CORS、更新基础 URL) - 建议在用户确认对迁移结果满意后,从项目中移除
migration-plan.md和migration-plan/
提问
在以下情况下,采取行动前先询问用户:
- 服务边界不明确——例如:“这些路由文件可以拆成 1 个服务,也可以拆成 3 个——你希望如何划分?”
- 不存在合适的 Encore 等效方案——例如 Redis 缓存层、自定义中间件链、WebSocket 处理程序
- 存在多种有效的迁移策略——说明各种选项及其权衡
- 进行任何可能产生副作用的 HTTP 调用之前——务必先询问
- 源代码含义不明确——当 Agent 无法确定代码的作用时,应先询问,而不是猜测
- 源系统似乎已发生变化——如果详细文件引用的文件已不存在或发生了重大变化
保护源系统
迁移期间绝不能修改源系统。请遵守以下规则:
- 绝不修改源文件——只读取,不编辑
- 绝不删除源文件——即使迁移已完成也不得删除
- 绝不写入源目录——所有输出都写入 Encore 项目
- 绝不对源系统运行破坏性命令(如删除表、删除队列等)
- 对源系统进行任何会更改状态的 HTTP 调用(POST、PUT、DELETE)之前,先征得用户同意
如果用户要求“清理”或“移除”旧系统,必须先明确确认,然后才能采取任何行动。源系统可能仍在为生产流量提供服务。
边缘情况
在不同单元之间移动端点
如果用户发现某个端点应属于其他迁移单元:
- 从源单元的详细文件中移除该端点对应的行
- 将其添加到目标单元的详细文件中
- 更新
migration-plan.md中的端点数量
迁移过程中拆分单元
如果处理某个单元时发现它规模过大:
- 为拆分出的部分创建新的详细文件(
migration-plan/<new-unit>.md) - 将待处理实体移至新文件(已迁移的实体保留在原文件中)
- 将新单元添加到
migration-plan.md摘要表中 - 按依赖顺序插入新单元(位于同一层级,排在原单元之后)
将单体迁移为多个 Encore 服务
一个迁移单元可能对应多个 Encore 服务。详细文件记录源分组,但“Notes”列可以注明目标 Encore 服务。迁移过程中应询问该单元是对应单个服务,还是应拆分到多个 Encore 服务中。
发现后源代码发生变化
如果详细文件引用的文件已不存在,或自发现阶段以来发生了重大变化:
- 向用户指出差异
- 询问是根据当前状态更新详细文件,还是跳过受影响的实体
- 如果选择更新,则重新评估复杂度并相应调整计划
故障排查
迁移期间的常见问题及其解决方法:
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 服务之间导入错误 | 服务之间直接导入 | 改用 ~encore/clients(TS)或服务客户端包(Go) |
| 数据库迁移失败 | SQL 语法不兼容 | 确认 Encore 使用 PostgreSQL,并调整 MySQL/SQLite 语法 |
| 未收到 Pub/Sub 消息 | 未注册订阅 | 确保在包级别声明订阅,而不是在函数内部声明 |
| 定时任务未触发 | 调度表达式无效 | Encore 使用标准 cron 表达式,请检查语法 |
encore run 基础设施报错 | 在函数内部声明了基础设施 | 将所有基础设施声明移至包级别 |
| 源项目与 Encore 的响应不同 | 缺少业务逻辑或错误处理方式不同 | 仔细比较响应结构,并检查边缘情况 |
| 无法验证端点 | 需要身份验证或存在副作用 | 向用户索取测试凭据,或标记为 manual validation needed |
| 客户端需要 422 或自定义错误正文 | Encore 的类型化端点使用固定的错误封装,并且验证错误会返回 400 | 将其记录为一项决策:接受 Encore 的格式,或对这些路由使用 api.raw |
| 迁移后浏览器客户端无法运行 | 源项目全局启用了 CORS | 在 encore.app 中添加 global_cors |
| 无法运行源项目进行 HTTP 对比 | 工具链过旧或基础设施不可用 | 改用契约测试套件(参见第 2 层替代方案) |
migration-plan.md 格式
使用此确切模板创建摘要计划文件。填写发现阶段获取的值。
# Migration Plan
## Source System
- **Path:** <source system path>
- **URL:** <source system local URL>
- **Framework:** <detected framework>
- **Language:** <detected language>
## Frontend (Out of Scope)
- **Detected:** <Yes/No>
- **Directories:** <list of frontend directories, or "None">
- **Framework:** <frontend framework if detected, or "N/A">
- **Note:** <any framework server-side code that WAS included in migration units>
## Target System
- **Path:** <encore project path>
- **URL:** <encore local URL>
- **Type:** Encore.ts | Encore Go
## Decisions
- **Behavior target:** <reproduce source exactly | fix bugs toward <contract>>
- **Error format:** <Encore native | api.raw for <routes>>
- **CORS:** <source setting → encore.app global_cors>
- <any other default chosen in autonomous mode>
## Migration Units
| Unit | Endpoints | DB Tables | Other | Complexity | Status |
|------|-----------|-----------|-------|------------|--------|
## Dependency Order
1. <ordered list of migration units>
状态值: pending、in progress、migrated、skipped、manual validation needed
复杂度值: Low(直接对应)、Medium(需要重构)、High(需要重新设计)
详细文件格式
在 migration-plan/<unit-name>.md 为每个迁移单元创建一个文件。使用此确切模板:
# Migration Unit: <unit-name>
## Source
- **Files:** <list of source files in this unit>
- **Depends on:** <other migration units, with their current status>
## Endpoints
| Endpoint | Method | Path | Status | Notes |
|----------|--------|------|--------|-------|
## Database
| Table | Complexity | Status | Notes |
|-------|------------|--------|-------|
## Tests
- **Source tests:** <test files and count>
- **Migrated:** <count of migrated tests>
## Validation Log
| Entity | Tests | HTTP Match | Evidence | Status |
|--------|-------|------------|----------|--------|
并非所有章节都是必需的——省略不适用于特定单元的章节(例如,密钥单元不会包含端点或数据库章节)。