t3code:一条命令生成TypeScript全栈CRUD模块的CLI工具实践

发布时间:2026/10/28 23:25:24
t3code:一条命令生成TypeScript全栈CRUD模块的CLI工具实践 如果你是一个全栈 TypeScript 团队的技术负责人或者一个每天都要开新模块、写 CRUD 的业务开发那你一定经历过这种时刻新建一个后台管理模块先建数据库模型再写校验逻辑接着写服务端路由、前端表单、列表页、详情页最后还要补一遍单元测试。模型、路由、页面、测试各写各的文件之间靠字段名手动对齐一次新建模块下来复制旧代码加改字段就能耗掉大半天。t3code 就是为了解决这个局面出现的。它是我给团队做的一整套代码生成命令行工具运行在 Node.js 环境面向 TypeScript 全栈技术栈下的日常业务开发负责把新模块、新仓库从人肉同步五处文件变成一条命令。这篇内容会从它解决的问题讲起把核心功能拆开聊实现思路也会把我在实际使用中踩过的坑和完整排查链路记录下来。1. 从复制粘贴地狱到第一个 CLI 原型t3code 的诞生动机1.1 每个新模块都是五处联动的重复劳动我们团队的技术栈比较统一Next.js 做前端应用tRPC 做端到端类型安全的 API 层Prisma 做数据库访问Tailwind 负责样式后端逻辑以 Route Handler 的形式挂在 Next.js 的路由体系里。这个技术栈有个很舒服的地方——类型可以从数据库一直穿透到前端组件改一个字段名编译期就能把漏改的地方全部揪出来。但舒服的前提是你得先把这一整套文件的骨架搭对。一个典型的业务模块比如用户订单管理需要同时维护以下内容schema.prisma里的数据模型定义以及对应的 migrationtRPC Router 里的查询、创建、更新、删除四个基础 procedure输入校验用的 Zod schema必须和 Prisma 模型字段保持一致前端页面列表页、表单页新建/编辑、详情页React Query 的 hook 封装以及页面上调用的 query key这五个部分之间靠字段名、类型、路由路径互相咬合。一个人肉复制的时候最常犯的错是Prisma 里字段叫customerNameZod 里写成了customer_name前端表单提交以后发现数据存不进去或者 Router 的 procedure 路径写错一个字母页面请求直接 404。这种错误本身不难修但排查链路长而且新人特别容易在同一个地方反复跌倒。1.2 与其继续复制不如把样板变成命令最开始我也想过用企业级低代码平台或者现成的脚手架。但评估下来发现这类平台往往要求你遵循它自己的数据模型和页面组织方式一旦遇到团队自定义的鉴权逻辑、自定义的布局组件、特殊的字段组件比如富文本、上传组件、字典选择器平台要么不支持要么你得花大力气做二次开发反而比手写更慢。后来我换了个思路既然团队的样板代码是高度稳定的那为什么不能把这套样板本身变成模板template再写一个命令把这些模板实例化到具体业务字段数据模型无非就是把customerName换成productName路由逻辑无非就是把order换成product页面结构几乎一模一样。于是我花了一个周末做了一版极简原型一个 Node.js 脚本读取用户输入的模块名和字段列表按照写死的模板字符串生成文件。第一版很粗糙但是跑完一个订单管理模块后我们几个核心开发都明显感觉到生成出来的代码比手工复制还要规范因为它是直接从团队当时的最佳实践样板抽出来的不是某个成员凭记忆写的。这个原型就是 t3code 的起点。名字里的 T3 既是 TypeScript 全栈技术栈的代号也有一点第三代工程化工具的意味——我们不再满足于创建项目而是要把项目内的高频开发动作也自动化。1.3 工具边界t3code 不做什么在动手膨胀功能之前我先把工具的边界划清楚。这是后来少走弯路最关键的一步不做可视化界面。命令行交互足够团队开发者都习惯终端做一个网页配置台反而增加维护成本。不接管运行时。t3code 只负责生成不负责运行。生成的代码就是普通的 TypeScript 文件可以被任意 IDE、构建工具、CI 流程正常处理绝不引入私有运行时依赖。不生成强业务逻辑。比如权限规则、复杂的订单状态机、特殊的计算逻辑这类代码靠模板很难抽象硬塞进模板只会让模板变成一团浆糊。不改动已有代码逻辑。它只负责在指定目录里创建新文件、或者在约定的配置文件里追加条目绝不反过来解析和修改用户手写的业务代码。边界清楚了后面所有设计冲突都好解决凡是超出生成范围的诉求一律不接宁可让用户手写两行代码也不在工具里堆砌万能配置。2. t3code 的核心能力拆解init、module add、doctor2.1 init用二十个问题换一份不纠结的项目骨架t3code init是团队接入新项目、或者新成员拉取仓库后本地初始化时用的命令。它做的事情很简单在当前目录生成一份完整的工程骨架包括目录结构、配置文件、CI 工作流、基础组件和工具函数。命令执行时是一组交互式问题而不是一大堆静默参数。比如$ t3code init ? 包管理器 (pnpm / yarn / npm): pnpm ? 是否需要 ESLint Prettier 预设: Yes ? 是否需要 GitHub Actions 工作流: Yes ? ORM 选择 (Prisma / Drizzle / 不使用): Prisma ? 组件库 (自定义 / shadcn/ui): shadcn/ui ? 是否开启路径别名 (/*): Yes ? 初始化 Git 仓库: Yes每个问题背后都有默认值全程回车也能得到一个合理配置。问题数量控制在 10 到 20 个之间我刻意设计成大部分情况直接回车就行因为选项过多会劝退使用者而真正的差异项其实就那么几个。init 的核心价值不是省去create-next-app那一步而是让团队的工程规范在项目初始化那一刻就自动对齐。比如我们要求所有新项目必须开启strict模式、必须配置好tsconfig的路径别名、必须带上一套统一的 ESLint 规则、必须让pnpm-lock.yaml进版本库。这些写在团队文档里没人看但写进 init 的模板里每个新项目都会自动遵守。有一个细节值得提init 生成的.env.example文件里会预留数据库地址、鉴权密钥等占位符文件名带.example后缀避免真实密钥被提交到仓库。这一步看着不起眼但真有不少团队在项目初始化时就因为顺手填了真实密钥把敏感信息传上了 GitHub。2.2 module add真正节省时间的杀手级命令如果说 init 是锦上添花那t3code module add就是整个工具里使用频率最高、也最直接节省时间的命令。一条典型的调用是这样$ t3code module add order ? 模块名称 (英文复数): orders ? 中文名称: 订单 ? 需要哪些基础字段: - orderNo: string (订单编号) - customerName: string (客户名称) - amount: number (金额) - status: enum (状态) ? 状态枚举值: PENDING, PAID, SHIPPED, COMPLETED, CANCELLED ? 需要生成哪些功能: 列表页 / 新建 / 编辑 / 详情 / 删除回答完这些问题工具会在项目里做以下几件事在 Prisma schema 中追加Order模型及其枚举定义并提示手动执行prisma migrate dev在server/api/routers/order.ts生成完整的 tRPC RoutergetAll、getById、create、update、delete每个 procedure 自带 Zod 校验在validations/order.ts生成与 Prisma 字段对应的 Zod schema在app/(dashboard)/orders/下生成page.tsx列表页、new/page.tsx新建、[id]/edit/page.tsx编辑、[id]/page.tsx详情在hooks/useOrders.ts生成 React Query 的封装 hook在tests/order.test.ts生成基础的路由单元测试骨架命令执行完终端会输出一个总结清单列出本次生成的文件路径、需要手动补充的空位比如权限判断、联动逻辑以及下一步建议执行的命令比如prisma migrate dev。使用者不需要去猜接下来该干嘛工具会告诉他。2.3 doctor让我这里跑不起来变成可诊断团队里最消耗精力的一句话是我这儿跑不起来。一半情况下是环境问题Node 版本不对、包管理器版本不对、.env文件缺失、Prisma client 没生成。t3code 加了一个doctor命令做环境体检$ t3code doctor - Node.js: v20.11.1 ✓ - 包管理器: pnpm v9.0.0 ✓ - TypeScript: 5.4.x ✓ - 配置文件: tsconfig.json ✓ / eslint.config.mjs ✓ - 环境变量: .env 缺失已使用 .env.example 对比缺少 DATABASE_URL / JWT_SECRET - Prisma client: 未生成在 server 目录下执行 pnpm prisma generate它不会自动修复只会给出准确的诊断信息和修复命令。为什么不做成自动修复因为在团队协作里环境问题往往伴随权限问题比如某个成员没有数据库访问权限工具擅自改配置反而会掩盖真正的阻塞项。doctor 只负责把问题的定位成本从半小时降到半分钟。2.4 命令层的取舍原则功能拆到这一步我总结出的取舍原则就是CLI 工具的命令必须满足一问一答一结果的直觉。init回答这个项目怎么开始module add回答这个新模块怎么创建doctor回答当前环境哪里不对没有第四条命令之前我一度想加一个t3code update来自动升级模板。后来发现模板升级的冲突点在于已经生成的文件要不要跟着改这个问题极其复杂干脆砍掉把模板更新交给代码评审流程去控制。工具做得窄反而更耐用。3. t3code 背后的实现思路模板体系、生成管线与幂等策略3.1 模板引擎选型为什么选择原样复制 插值而不是全量渲染t3code 的生成核心不是简单地做字符串替换。项目里的文件可以分为两类一类是静态样板就是那种每个项目都一模一样、完全不需要动态内容的文件另一类是动态模板需要根据用户输入的模块名、字段、枚举值来生成不同的内容。我对比过几种方案纯字符串拼接最简单但可读性差到没法维护生成一个页面文件要写上百行字符串模板。模板引擎全量渲染如 Handlebars、EJS动态能力强但会遇到一个致命问题——模板文件里的语法本身就包含大量 JS/TS 字符比如{、}、${}、%等容易和模板引擎自身的语法冲突出现转义地狱。原样复制 插值对于静态样板直接复制文件对于需要动态化的内容只在标记的位置做插值。这是 t3code 最终采用的方案稳定性比全量渲染高了一个量级。具体落地时我用的是轻量级模板标记[[...]]而不是默认的{{ }}。原因很简单TypeScript 的类型声明里到处是花括号默认语法会和interface、type、对象字面量混淆用[[moduleName]]这种标记几乎不会跟正常代码撞车。一个动态模板片段长这样以 Zod schema 生成为例// validations/template.zod.ts.tpl import { z } from zod; export const [[moduleName]]Schema z.object({ [[fields]] }); export type [[ModuleName]]Input z.infertypeof [[moduleName]]Schema;[[fields]]这个位置在生成时会被替换成由 Conductor 传入的字段校验列表。这里最重要的是模板里只有动态插入点使用特殊标记其余代码保持团队日常写法的原样。这样模板文件本身就可以被 IDE 正常解析、被 Prettier 格式化还能进代码评审。3.2 生成管线四步走scan、plan、write、reportt3code 的代码生成不是拿到输入直接写文件那样一把梭而是拆成了四个阶段。这个设计是从编译器的思路借鉴来的好处是每阶段可以独立测试、独立报错。第一阶段scan扫描读取项目当前状态。这一步很关键决定了后续生成会不会破坏已有文件。工具会检查Prisma schema 里是否已有同名模型、目标路由是否存在、页面目录是否冲突、hooks 文件是否存在等。第二阶段plan规划根据用户输入和扫描结果生成一个文件操作计划。计划的每一项就是一个文件操作是创建、是追加、还是跳过。这个阶段会把所有冲突集中暴露出来比如订单模块已存在是否覆盖用户在正式写盘之前就能决定。第三阶段write写盘按规划执行文件操作。所有操作是原子的——要么全部成功要么报错后回滚掉已经创建的文件不留半成品。回滚逻辑很重要否则用户执行到一半发现某个文件冲突CtrlC 终止后项目里会残留一堆残缺文件。第四阶段report报告把生成结果、待办事项、下一步命令输出给用户。报告里还会提示哪些文件被跳过、哪些位置需要手写补全。这一步直接影响使用体验因为用户如果不知道接下来要干什么生成得再快也没用。3.3 覆盖策略与幂等性重复执行不应该是灾难CLI 工具最容易翻车的就是二次执行。module add order第一次执行没问题用户手贱又执行了一次结果列表页被覆盖、手写的过滤逻辑全没了。这在团队场景里是不可接受的。我设计的覆盖策略分三个档位纯生成文件只要内容哈希一致就跳过不动避免无意义的 write 和 git diff 噪音。需要追加的文件如 Prisma schema、路由汇总文件执行前先判断目标内容是否已存在。已存在的同名 model 或路由条目绝不重复追加。可能被用户修改过的文件生成时记录内容哈希。第二次生成时如果发现用户改过不再静默覆盖而是提示用户进入--diff模式或直接跳过该文件。这套策略的核心原则是默认保守除非显式要求覆盖。用户只有明确传--force才会无条件覆盖生成文件。实测下来这个默认策略大大降低了误伤事故也让工具在团队里的信任度高了不少。3.4 命名与目录规范的隐藏工作代码生成工具最容易忽视的反而是命名转换。用户输入一个order但不同文件、不同上下文里需要的形态完全不同文件名order.ts、orders/page.tsx复数kebab-casePrisma 模型名Order单数PascalCaseZod 变量名orderSchema单数camelCase路由路径orders复数类型名OrderInput、OrderWhereInputPascalCase表格列名订单编号中文展示t3code 内部维护了一套命名引擎统一处理 singular、plural、pascal、camel、kebab、中文展示名之间的转换。这块不写进模板而是作为独立的工具函数供所有模板调用。没有这套命名引擎之前我遇到过最离谱的生成错误模块名带连字符order-itemPrisma 模型名被生成了OrderItem正确但路由文件名却是order-item.tsx正确而导入路径却拼成了order_item错误。有了命名引擎这种问题从源头消失了。另一个隐藏工作是生成后自动格式化。所有文件写盘后t3code 会调用项目里已有的 Prettier 和 ESLint 做一次自动修复。这一步不是为了省去提交前的手工格式化而是确保生成的代码直接通过 CI 检查不会因为缩进或分号问题导致流水线变红。4. 三个最典型的坑与完整排查链路4.1 坑一泛型代码被模板引擎吞掉的排错全过程第一次用 Handlebars 做模板时我遇到一个诡异的报错所有.ts模板文件在执行时抛ParseError而报错位置指向的代码看起来完全正常。报错内容大致是Expecting CLOSE_RAW_BLOCK ...。排查过程是这样的我先把出错的模板文件逐行缩减寻找触发报错的最小片段。最终定位到一行代码export function createHandlerT extends Recordstring, unknown() { // ... }问题就出在Recordstring, unknown里的}}上。Handlebars 把连续的}}识别成了模板表达式的结束标记导致它认为前面有一个未闭合的表达式于是整个解析直接失败。这个坑的本质是模板语法和宿主语言语法撞车了。不是我写错了模板而是这个模板引擎选型本身就不适合用来生成 花括号密集型的 TypeScript 代码。解决办法有两步。第一步把模板引擎从 Handlebars 换成 原样复制 插值方案动态标记改为[[...]]从语法层面避开了冲突。第二步在模板文件里如果确实需要展示字面意义的[[或]]使用[[[ ]]]这种转义写法。这两步做完泛型代码、对象字面量、JSX 表达式一律不再出现转义问题。这个坑给我的教训是做代码生成工具之前先花十分钟想清楚模板语法与目标语言的符号冲突这个基本问题能省掉后面几天排错时间。4.2 坑二生成后的代码在 CI 里大量 ESLint 报错另一个印象深刻的坑是本地生成一切正常代码也能编译但推到远程后 ESLint 检查大量报错。大部分错误类型是indent缩进、object-curly-spacing花括号内空格、quote-props属性引号。一开始我以为是模板里的代码格式不对后来发现根本不是内容问题而是模板里的缩进长度和Prettier 的规则不一致。比如模板文件开头用两个空格缩进但生成时把片段插入到某个嵌套层级更深的文件里相对缩进就乱了。因为插入位置不同同样的模板片段在列表页里要多缩进两层在 hooks 文件里只需要一层。当时我在write阶段只做了文件拼接没有做格式化收尾。排查链路是先看 ESLint 报错文件发现全部集中在生成目录再尝试在模板里手工调整缩进发现改完 A 文件又弄坏 B 文件最后确认问题本质是缩进依赖上下文模板无法静态确定。解决方案分两层。第一层生成完成后统一执行项目已有的 Prettier把格式问题一次性解决。第二层在模板设计时就规定动态片段尽量保持顶格 占位符的写法不做缩进假设具体缩进交给 Prettier 处理。这个改动让生成代码的 ESLint 通过率直接回到了 100%。4.3 坑三二次生成覆盖手写代码导致改动丢失这是我们内部曾经发生的最严重的一次事故。一位同事用module add customer生成了客户管理模块然后花了半天时间在列表页里手写了一个按城市筛选的联动下拉框。第二天他想再补一个字段重新执行了module add customer因为没有--force概念这是 v1 的代码工具直接覆盖了整个目录。他手写的筛选逻辑全没了只能靠 git 历史找回。这个事故让我重新审视了生成工具的定位工具生成的代码在写盘那一刻之后就不再属于工具了它属于用户。工具凭什么在用户已经编辑过的文件上再次覆盖排查后发现问题的根源不是我写覆盖逻辑时粗心而是我压根没有文件所有权这个概念。v1 版本里生成文件的数量、路径、内容全部由模板引擎决定工具只要运行就会写盘完全没有区分首次生成和再次生成。修复方案就是前面提到的三级覆盖策略又加了一层保险所有通过module add生成的文件都会在文件头部写一段注释标记// Generated by t3code. Manual changes will be preserved on regenerate. // generated再次生成时工具读取标记识别所有权没有标记的文件说明是用户新建或大幅改动的一律默认跳过。同时我们把plan阶段暴露的所有冲突项全部推给用户确认而不是让工具自动决策。从这次事故之后t3code 再也没有出现过覆盖用户手写代码的案例。4.4 排查思路复盘如何快速定位这类工具的故障把这三个坑放在一起看能总结出一个通用排查思路遇到代码生成工具的故障先把故障发生在哪个阶段定死。如果是scan 阶段的问题报错信息会集中在读不到文件解析 schema 失败路径不存在这类方向是检查项目目录结构和配置文件。如果是plan 阶段的问题典型症状是文件操作计划里出现不合理的覆盖/跳过项方向是检查冲突检测逻辑和命名引擎。如果是write 阶段的问题典型症状是文件内容不对但路径对方向是查模板插值、转义和缩进处理。如果是report 阶段的问题典型症状是文件写对了但用户不知道下一步做什么方向是查报告信息的覆盖度和准确性。用这个框架绝大多数问题可以在五分钟内定位到具体模块。这也是我把生成管线拆成四阶段的最实际收益——不是架构好看而是排查故障时每一层都可以单独验证。5. 团队落地后的实测数据与方法论沉淀5.1 一组可量化的效果对比t3code 在公司内部稳定用了半年后我统计了一次比较完整的数据。以前纯手工开发一个标准的 CRUD 模块从建表到页面全部跑通平均要 40 到 60 分钟。用 t3code 生成骨架后这个时间压缩到 10 分钟以内省下的时间主要花在补业务逻辑和调样式上。数据层面的对比更直观指标使用前使用后新建标准 CRUD 模块耗时40 - 60 分钟8 - 10 分钟模块内字段命名不一致问题每周至少 2 次基本绝迹新建模块的 ESLint 首轮通过率约 70%接近 100%新人上手跑通第一个模块的时间2 - 3 天0.5 - 1 天模块目录结构偏差经常出现全团队一致我最在意的其实是最后一行目录结构一致。这个收益不直接体现在某一次开发速度上但长期累积下来代码评审的效率、跨模块重构的难度、团队成员互相接手代码的成本都因为每个长都一样而大幅下降。这比单纯节省半小时更有价值。5.2 模板即规范让最佳实践长进代码里t3code 给我最大的启发是团队规范不应该只写在 wiki 里更应该固化在模板里。wiki 上的规范要靠人读、靠人记、靠人自查效果很有限。模板里的规范是强制性的只要代码从 t3code 生成出来它天然就是规范的。所以后来我们把代码评审的一部分重心从逐行纠错变成了模板更新评审。团队成员发现某个模块里更好的写法不再只在当前代码里改掉而是会顺手把模板同步更新。这样改进一次以后所有新模块都自动受益。模板成了团队经验的载体而 t3code 成了经验的分发管道。当然模板更新同样要走评审。我们的做法是模板仓库和主项目仓库分开管理模板的每次变更都发起 Pull Request说明变更原因和影响范围。这个流程看着重实际上每周也就一两次非常值得。5.3 下一步把 t3code 从脚手架升级为团队知识库工具目前的状态已经稳定我后续的规划主要有两个方向。第一个方向是接入 AI 辅助。现在的交互方式是用户手动输入字段、类型、枚举值未来可以让用户用自然语言描述模块需求比如订单模块要有编号、客户名、金额、状态状态有已支付待发货已完成工具自动转换成module add的参数。这块的工作量主要不在模型而在于把自然语言解析结果可靠地对接到现有的命名引擎和模板体系。第二个方向是让模板支持更细粒度的能力组合。目前的module add是整块生成的比如生成列表页就会带上搜索栏、分页、批量删除按钮。未来想拆成能力插件要搜索就挂搜索组件要导入就挂导入按钮这样模块生成的自由度更高适应性也更好。从最初那个周末原型到现在的稳定工具t3code 的演进过程让我彻底认同了一件事工程效率工具的终极目标不是替人做决定而是把团队的隐性知识和重复劳动压缩成一条简单命令。模板会一直改功能会持续加但这个定位不会变。也希望这篇内容能给你一些做代码生成工具、或者搭建团队内部工程化体系的参考。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询