Wasp 教程动作执行器(TACTE):从 MDX 教程文档到可运行 Wasp 应用的自动化流水线
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
导读
Wasp 官方文档内置了一套引导开发者从零构建完整 Wasp 应用的分步教程,每一节教程中都嵌入了<TutorialAction>组件来标注"创建新 Wasp 应用""添加认证""创建 Task 实体"等机器可执行动作。web/tutorial-actions-executor目录下的 Tutorial Actions Executor(TACTE)正是负责读取这些教程文件、抽取动作并按序执行、最终产出完整可运行 Wasp 应用的命令行工具。阅读本文,你将掌握 TACTE 的三大命令(generate-app、edit-patch-action、list-actions)的完整用法、教程 MDX 文件的动作标注格式、Patch 文件管理机制,以及它如何借助 Git 提交历史实现"修改某个动作并自动重放后续动作"的编辑能力。
1. 背景:TACTE 要解决什么问题
Wasp 文档的分步教程覆盖了从项目初始化、页面、实体、查询、动作到认证的完整开发过程。这些教程文档以 MDX 形式存放于web/docs/tutorial/,每个文档步骤用编号前缀命名(如01-create.md、02-project-structure.md、03-pages.md、04-entities.md、05-queries.md、06-actions.md、07-auth.md),并通过TutorialAction组件标注每一步对应的可执行动作。
TACTE 的核心使命(见 README.md)是:读取这些教程文件,抽取其中定义的<TutorialAction>动作,按文档顺序逐一执行,最终生成一个完整可用的 Wasp 应用。这意味着教程文档不只是给人读的文字,还变成了一份可以被机器执行的"食谱"。
<TutorialAction>组件本身定义在 web/docs/tutorial/TutorialAction.tsx 中,其注释明确说明它与 TACTE 的关联:在开发模式下它渲染出带action类型和id的调试信息条(便于作者排查),在生产环境则直接透传子内容。该组件支持三种动作类型(源码中的ActionProps类型):
INIT_APP:初始化应用,需额外提供starterTemplateName属性;APPLY_PATCH:应用一个 Git Patch;MIGRATE_DB:执行数据库迁移。
组件注释还强调:修改动作类型时,必须同步更新 src/actions/actions.ts 中的 TypeScript 类型定义——这正是文档组件与执行器之间"契约"的体现。
2. 整体架构:从 MDX 到可运行 App 的执行流水线
TACTE 是一个基于 Commander,注册了generate-app、edit-patch-action、list-actions三个子命令。从源码结构看,完整流水线可分为三个阶段:
2.1 抽取:解析 MDX,定位 TutorialAction 节点
extract-actions/mdxParsing.ts 使用mdast-util-from-markdown配合micromark-extension-mdx-jsx扩展把 MDX 文件解析为 AST,随后 astTraversal.ts 通过unist-util-visit遍历 AST,找出所有名为TutorialAction的mdxJsxFlowElement节点,并读取其属性:
id与action为必填属性,缺失时直接抛出错误;starterTemplateName仅在INIT_APP动作中使用。
文件读取顺序由 fileOperations.ts 保证:只读取.md/.mdx文件,并按文件名数字前缀(01-、02-…)升序排列,从而保证动作的执行顺序与教程步骤一致。
2.2 映射:从 JSX 节点到内部 Action 模型
nodeMapping.ts 负责把 AST 节点映射为 actions.ts 中定义的联合类型Action = InitAppAction | ApplyPatchAction | MigrateDbAction。每种动作都携带id(唯一标识)与sourceTutorialFilePath(来源文件):
| 动作类型 | 额外字段 | 执行时行为 |
|---|---|---|
INIT_APP | waspStarterTemplateName | 调用wasp new <app-name> -t <template>创建应用并初始化 Git 仓库 |
APPLY_PATCH | patchFilePath、displayName | 用git apply应用对应的 Patch 文件 |
MIGRATE_DB | 无 | 运行wasp db migrate-dev --name <action-id>生成并应用迁移 |
2.3 执行:逐动作执行并逐个提交
核心执行循环位于 execute-actions.ts。对每个动作按kind分发处理,并在动作完成后统一调用commitActionChanges提交:
INIT_APP→ init.ts 中的initWaspAppWithGitRepo:先清空旧目录,再调用 waspCli.ts 中的waspNew(即wasp new <name> -t minimal),随后执行git init并把主分支重命名为main;APPLY_PATCH→ 先尝试applyPatchForAction(底层即 git.ts 的git apply --verbose),若失败则进入"重新生成 Patch"流程(见第 5 节);MIGRATE_DB→ 调用waspDbMigrate执行wasp db migrate-dev --name <migrationName>。注意 waspCli.ts 中对该命令显式设置了stdio: ["ignore", "pipe", "pipe"],源码注释说明这是为了避免非交互环境(如 e2e 测试)下命令因等待 stdin 输入而挂起。
每个动作执行后都会以动作的id作为提交信息生成一个独立的 Git commit(见 git.ts 的commitAllChanges:git add .+git commit -m <message>)。这一设计是后续edit-patch-action能够"回退重放"的基础,也是 e2e 快照测试校验 Git 历史的依据。
3. 命令一:generate-app —— 一键生成完整应用
npm run generate-app # 可选:指定自定义的 Wasp CLI 二进制/命令 npm run generate-app -- --wasp-cli-command wasp该命令执行以下流程(generate-app/index.ts):
- 读取教程目录下所有按编号命名的教程文件;
- 从每个文件的
<TutorialAction>组件中抽取动作; - 按序执行每个动作(初始化应用、应用 Patch、迁移数据库);
- 全部完成后输出成功信息,并给出生成应用的目录路径。
命令会先打印Found N actions in tutorial files.以确认抽取到的动作总数。如果某个 Patch 应用失败,generate-app会暂停并进入人工解决流程(详见第 5 节)。
在仓库中运行:package.json中预设的脚本已绑定默认参数--app-name TodoApp --tutorial-dir ../docs/tutorial,即直接针对web/docs/tutorial/下真实教程生成TodoApp应用。
4. 命令二:edit-patch-action —— 修改某个 Patch 并自动重放后续动作
当教程内容调整后,某个 Patch 可能不再准确。edit-patch-action让你修改指定动作的代码,并自动把后续所有动作重新应用到新的基础上:
# 非交互式:按 ID 直接指定 npm run edit-patch-action -- --action-id "create-task-entity" # 交互式:从列表中选择要编辑的动作 npm run edit-patch-action # 可选参数: # - 跳过编辑前的应用生成 npm run edit-patch-action -- --skip-generating-app # - 指定自定义 Wasp CLI npm run edit-patch-action -- --wasp-cli-command wasp该命令的完整逻辑见 edit-patch-action/index.ts:
- 生成应用(除非传入
--skip-generating-app):先完整执行一遍generate-app,使每个动作都对应一个独立的 Git commit; - 回退到目标动作:通过 git.ts 中的
findCommitSHAForExactMessage按提交信息(即动作id)精确查找对应 commit,然后git switch --force-create fixes <commit>创建一个名为fixes的分支并切到该动作的提交; - 进入编辑态:执行
git reset --soft HEAD~1把该 commit 的改动放回暂存区,接着调用 git.ts 的askUserToEditAndCreatePatch——如果设置了$EDITOR环境变量,editor.ts 会询问是否用该编辑器打开生成的应用目录(./.result/<app-name>),随后用@inquirer/prompts的confirm提示你改完后回车确认; - 生成新 Patch 并提交:把工作区改动导出为新的 Patch 文件(内部通过"临时提交 →
git show→git reset --hard HEAD~1"实现),再应用该 Patch 并以动作id重新提交; - 重放后续动作:把
fixes分支 rebase 回main分支之上(git switch main+git rebase fixes)。如果后续动作与你的修改产生冲突,命令会暂停并提示你手动解决后回车继续; - 回写 Patch 文件:
extractCommitsIntoPatches遍历所有APPLY_PATCH动作,从各自的 commit 重新生成 Patch 内容并写回patches目录,保证磁盘上的 Patch 文件与新的提交历史保持一致。
其中"选择要编辑的动作"支持两种方式:传入--action-id时精确匹配(找不到会报Apply patch action with ID "..." not found.),未传参时用@inquirer/prompts的select弹出交互式列表。
5. 命令三:list-actions —— 盘点全部教程动作
npm run list-actionslist-actions/index.ts 会读取全部教程动作,按来源文件名分组展示每个动作的id和kind,并按类型着色:INIT_APP黄色、APPLY_PATCH绿色、MIGRATE_DB蓝色。这在修改教程、核对动作完整性时非常实用,输出形如:
04-entities.md - prisma-task (APPLY_PATCH) - migration-add-task (MIGRATE_DB)6. 公共选项:三个命令的必填配置
三个命令共享 tacteCommand.ts 中定义的公共选项,用于配置教程应用生成环境:
| 选项 | 说明 | 默认值 | 是否必填 |
|---|---|---|---|
--app-name <name> | 要生成的应用名称(也是输出目录下的子目录名) | — | 是(makeOptionMandatory) |
--output-dir <path> | 应用生成目录 | ./.result | 否 |
--tutorial-dir <path> | 包含教程 MDX 文件的目录 | — | 是(makeOptionMandatory) |
另外 commonOptions.ts 提供--wasp-cli-command <command>选项,默认值为wasp,用于覆盖执行wasp new/wasp db migrate-dev时使用的 CLI 命令(例如传入wasp-cli或自定义路径)。
示例:
npm run generate-app -- --app-name MyApp --output-dir ./custom-output --tutorial-dir ./my-tutorial路径解析规则见 tutorialApp.ts:生成应用的目录为<output-dir>/<app-name>,Patch 目录固定为<tutorial-dir>/patches。
7. Patch 文件管理:命名、缺失与冲突处理
7.1 命名规范
Patch 文件必须存放在教程目录下的patches子目录中。文件名由来源教程文件名(去扩展名)与动作id拼接而成,格式为<tutorial-file-name>__<action-id>.patch(见 actions/index.ts 的getPatchFilename)。
以仓库真实数据为例,web/docs/tutorial/patches/ 下的文件03-pages__prepare-project.patch、04-entities__prisma-task.patch、06-actions__action-create-task.patch等,分别对应03-pages.md中的prepare-project动作、04-entities.md中的prisma-task动作。每个文件内容是一份标准 Git Diff,例如 04-entities__prisma-task.patch 展示了向schema.prisma追加Task模型的改动。
7.2 缺失或无法应用时的交互流程
如果某个 Patch 文件缺失或git apply失败,generate-app会暂停,并执行 actions/git.ts 中的regeneratePatchForAction:
- 若旧 Patch 文件存在,先删除它;
- 打开生成的应用目录(
./.result/<app-name>),提示你按当前<TutorialAction>的描述手动修改代码; - 回车确认后,工具会把工作区改动导出为新的 Patch 文件写入
patches目录,并自动以该动作id提交; - 之后重新应用新 Patch 并继续后续动作。
注意:流程中所有提交都由执行器自动完成,不要在生成的应用里手动提交,否则会破坏"每动作一提交"的对应关系。
7.3 与 LLM 配合的 Human-in-the-Loop 工作流
README 明确给出了一套与 LLM 协作的流程:让generate-app保持运行,采用"人在回路"模式——当提示指向当前动作时,请 LLM 修改./.result/<app-name>中该动作对应的代码,人工审查改动后在终端确认,如此反复直至命令成功跑完。这也是在教程需要批量更新、Patch 大面积失效时,借助 LLM 自动生成新 Patch 的实用方式。
8. 教程文件格式:如何用<TutorialAction>标注动作
教程文件是 MDX,动作通过 JSX 组件<TutorialAction>标注,并用它包裹与该动作关联的教程正文:
# Step 4: Create Task Entity In this action, we'll create the Task entity: <TutorialAction id="create-task-entity" action="APPLY_PATCH"> ```prisma model Task { id Int @id @default(autoincrement()) } ``` </TutorialAction>关键属性:
id:动作的唯一标识,同时也是 Git 提交信息(必须唯一);action:动作类型,可选INIT_APP、APPLY_PATCH、MIGRATE_DB。
在真实仓库中,INIT_APP的用法可见 01-create.md:<TutorialAction id="create-wasp-app" action="INIT_APP" starterTemplateName="minimal">包裹了wasp new TodoApp -t minimal的命令;MIGRATE_DB的用法可见 04-entities.md:<TutorialAction id="migration-add-task" action="MIGRATE_DB" />是自闭合标签,紧随其后的正文是wasp db migrate-dev命令。
e2e 测试夹具(e2e-tests/fixtures/tutorial/)提供了最简可运行示例:01-init.md(INIT_APP,starterTemplateName="minimal")、02-patch.md(APPLY_PATCH 新增src/testUtils.ts)、03-migrate.md(APPLY_PATCH 新增Post模型 + MIGRATE_DB 迁移),与之配套的 Patch 文件位于e2e-tests/fixtures/tutorial/patches/。
9. 测试体系:单元测试 + e2e 快照测试
项目同时包含单元测试与端到端(e2e)快照测试,运行全部测试:
npm run test9.1 单元测试
tests/目录下的单元测试覆盖了解析与执行的关键环节:
extract-actions/:测试 MDX 解析、AST 遍历、节点映射与文件操作(如数字前缀排序);actions/:测试 Git 相关操作与动作创建工厂;commands/:测试list-actions的分组展示逻辑。
9.2 E2E 快照测试
e2e 测试(generate-app.test.ts)验证完整的教程动作执行流程,采用快照对比:首次运行把执行器的输出(生成的文件清单、src/testUtils.ts内容、schema.prisma、Git 提交历史等)保存为"快照",后续运行再与快照比对,确保行为没有意外变化。
目录结构约定:
- 测试夹具:
e2e-tests/fixtures/tutorial/(最小化的教程文件); - 生成输出:
e2e-tests/.result/; - 快照存储:
e2e-tests/__snapshots__/。
测试通过WASP_CLI_COMMAND环境变量指定所用的 Wasp CLI(默认wasp-cli),并在比对前把 Prisma 迁移文件名中的时间戳归一化处理,避免时间戳差异导致快照误报。当你有意修改了执行器行为、需要更新快照时,使用更新模式:
npm test -- -u10. 总结:TACTE 的价值与适用场景
TACTE 把"撰写教程"与"验证教程"合二为一:文档中的每一步都变成可执行的机器指令,Git 提交历史成为动作的"账本",Patch 文件成为动作的可复用载体。从源码实现看,这套设计带来了三个直接收益:
- 教程可验证:任何对教程内容的改动都可以通过
generate-app立即验证能否生成可运行应用,list-actions帮助快速盘点动作全貌; - 改动可追溯:每个动作一个 commit,
edit-patch-action通过分支 + rebase 实现了对历史步骤的安全修改与自动重放; - 人力成本可替代:配合 LLM 的 human-in-the-loop 流程,即使 Patch 大面积失效,也可以半自动地重建整套教程产物。
如果你需要为 Wasp 的教程体系贡献内容或排查生成问题,从 web/tutorial-actions-executor/README.md 出发,结合 src/commands/generate-app/execute-actions.ts 与 src/extract-actions/mdxParsing.ts 两处核心实现,即可快速建立完整的代码心智模型。
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考