TinaCMS v4 开发指南packages/v4/AGENTS.md架构、CLI 边界与工程规范全解析【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms导读本文以 TinaCMS 仓库中 packages/v4/AGENTS.md 为骨架系统梳理 v4tinacms/tinacms的包结构、命令体系、硬性规则、类型约束、错误处理、React/JSX 规范与无障碍A11y要求并结合packages/v4/下的真实源码、测试与配置逐条印证。读完本文你将理解 v4 与 v3 的关键差异CLI 退出构建管线、tina-lock.json提交而非构建产物、掌握tinacmsbin 的合法命令边界、熟悉 v4 的 branded type 模式、unknown错误处理、无障碍命名规则等工程规范可直接用于 v4 源码阅读、插件开发与代码评审。一、AGENTS.md的定位v4 目录的“代理指令”packages/v4/AGENTS.md是一份面向 AI Agent 与贡献者的目录级说明文档规定了在packages/v4/目录下工作的所有行为边界。它开篇即指明三条“先读”线索README.md—— 包地图v4 发布哪些包、发布规则以及CLI 为什么被排除在构建管线之外独立的 v4 架构规格仓库tinacms/tinacmsv4-docs官方文档声明存在于外部仓库本文以仓库内证据为准不展开其内部内容建议从CONTEXT.md开始再读 ADR 系列tinacms/tinacms/_docs/是 v4 最终形态的事实来源架构、插件、字段插件、逐字段规格。从仓库看packages/v4/tinacms/tinacms/_docs/确实存放了architecture.md、plugins.md、field-plugins.md以及string-field.md、rich-text-field.md、array-field.md、boolean-field.md、datetime-field.md、number-field.md、select-field.md等逐字段规格文档。也就是说AGENTS.md 是“规则层”_docs/是“事实层”二者配套使用。二、v4 的包结构三个包一份包地图AGENTS.md给出了 v4 目录下的三个包包名路径职责tinacms/tinacmspackages/v4/tinacms/tinacmsv4 运行时 CLI 合并在一个包里。private: true版本4.0.0-alpha.x。通过子路径导出/react、/client、/server、/local-data-layer以及框架适配器/adapters/next、/express、/astro、/honotinacms/rich-textpackages/v4/tinacms/rich-textPlate 富文本编辑器。值契约value contract将编辑器与存储格式分离必须守住这条边界见src/boundary.test.tstinacms/uipackages/v4/tinacms/ui基于 shadcn/ui 的共享 UI 组件2.1 与 v3 布局的对照源自 README.md维度v3当前v4根运行时 npm 名tinacmstinacms/tinacms工作区路径packages/tinacmspackages/v4/tinacms/tinacms发布状态支持模式仅修 bug 和安全问题预发布4.0.0-alpha.xprivate: true直至 alpha 发布CLItinacms/cli独立包提供tinacmsbin并入tinacms/tinacms由它提供tinacmsbintinacms/tinacms的 package.json 印证了这一点version: 4.0.0-alpha.0、private: true并在bin字段声明tinacms: ./bin/tinacms.mjsexports字段逐条列出/react、/admin、/preview、/client、/server、/local-data-layer、/local-data-layer/vite与四个框架适配器子路径且当前 alpha 脚手架直接通过exports指向src/*ADR-001 规定正式版编译到dist/。AGENTS.md特别强调v3packages/tinacms与packages/tinacms/*处于支持模式禁止为 v4 功能改动 v3 包Level 适配器与外部集成也不得迁入本仓库README.md中列有其外部归属。2.2 富文本的值契约边界boundary.test.ts的实际约束AGENTS.md用一句话概括了tinacms/rich-text的核心设计而测试 boundary.test.ts 用代码把这条边界固化成可验证的规则编辑器只能引用tinacms/schema-tools与tinacms/ui两个共享包不得 import 宿主运行时也不得 import 任何读写存储格式的包相对导入不得逃出src/目录resolved.startsWith(SRC)检查测试自证其有效源码树内总导入数大于 200tinacms/*导入非空——防止“正则失配导致检查空转”。这让“值契约分离编辑器与存储格式”不再是口头约定而是一条由 vitest 守护的包边界。三、命令体系在包目录内运行AGENTS.md要求所有命令在包目录内执行并列出六个命令pnpm dev # 仅 tinacms/tinacms —— vite playgroundplayground/ pnpm test # vitest pnpm test:e2e # 仅 tinacms/tinacms —— playwright pnpm types # tsc tsconfig.test.json pnpm build # tinacms-scripts build pnpm codegen # 仅 tinacms/tinacms —— 重新生成 playground 的 tina-lock对照 tinacms/tinacms/package.json 的 scripts 段除test:e2e外全部一一对应且能看到更多细节dev实际为vite playground即用 Vite 直接启动 playground 应用codegen为node ./bin/tinacms.mjs codegen --root playground说明 codegen 面向 playground 目录生成tina/tina-lock.jsontypes是双跑tsc后再用tsconfig.test.json校验测试类型。AGENTS.md明确 playgroundtinacms/tinacms/playground/是手动测试台用于在浏览器中验证运行时/编辑器改动。仓库中 playground 确实包含tina/config.ts、tina/tina-lock.json、src/app.tsx、src/preview/与vite.config.ts并与同目录examples/barebones一个最小可运行示例含tina/config.ts、tina/tina-lock.json与自定义字段rating-field.tsx互相印证。四、硬性规则CLI 边界、lock 文件与组件管理4.1tinacmsbin 只写“人会提交的文件”这是 v4 与 v3 最本质的架构差异。AGENTS.md的规则原文是tinacmsbin 只写一个人会提交的文件init、codegen。它绝不能包装进程、开放端口或产生构建产物。没有dev或build命令。README.md 用一张命令表进一步展开命令写入内容是否允许tinacms inittina/config.ts、插件注册、admin 路由允许tinacms codegentina/tina-lock.json允许tinacms codegen --check不写入lock 过期时以退出码 1 结束允许tinacms dev—否。应运行框架自己的开发服务器Vite 插件与适配器负责其余工作tinacms build—否。lock 已提交无需构建设计动机README 原文概括v3 中tinacms dev -c next dev让 Tina 成为框架的父进程tinacms build next build让 Tina 成为构建步骤——Tina 的任何故障或工具链冲突都会阻断与内容无关的构建。v4 反转了这一关系项目拥有自己的管线由项目调用 Tina。每个能力因此挂载到项目已有的服务器上本地数据层是项目加入自身配置的 Vite 插件或是一个适配器路由dispatchContentRequest不绑定任何传输层为其他打包器写宿主只是一个小文件RPC 处理器是(Request) PromiseResponse框架适配器把它挂为项目的一个路由admin UI 是一个 React 组件由项目在自己的路由上渲染而不是构建时拷进public/的 bundle。4.2tina-lock.json是提交物不是构建产物tina-lock.json是已提交文件而非构建输出ADR-016因此 CI 与项目部署从不运行tinacmsbintinacms codegen --check作为防漂移守卫存在是项目主动选择加入的检查。仓库中 playground/tina/tina-lock.json 与 examples/barebones/tina/tina-lock.json 均已提交正是这一规则的实例。4.3 其他硬性规则不要为 v4 功能触碰 v3 包packages/tinacms、packages/tinacms/*v3 仅接受 bug 与安全修复不要把 Level 适配器或外部集成迁入本仓库其归属以 README.md 为准仓库内列出mongodb-level、sqlite-level、upstash-redis-level等外部仓库shadcn 组件一律在tinacms/ui/内通过pnpm dlx shadcnlatest add component添加或更新不得手写 shadcn 已提供的原语。仓库中 tinacms/ui/src/components 下的button.tsx、input.tsx、select.tsx、tooltip.tsx、sheet.tsx、sidebar.tsx等正是 shadcn 风格的受控组件。五、类型规范零any与 Branded Type5.1 零any纪律不允许任何any不做注解、不做断言。改用unknown再收窄或写出真实类型tinacms/tinacms/src当前零any必须保持rich-text/src/plate中的any是继承自 Plate 的代码不得新增动到这些文件时顺手移除。5.2 Branded Type标识符不是裸string规则标识符要有具体的 branded type而不是裸string。使用 core/brand.ts 的Brand并为每个 ID 提供在边界处做校验的to*构造函数——cast 只存在于构造函数里别无他处// core/brand.ts export type BrandT, K extends string T { readonly __brand: K }; // core/field/address.ts export type FieldAddress Brandstring, FieldAddress; export const toFieldAddress (path: string): FieldAddress { invariant(path.length 0, field-address-empty, ...); return path as FieldAddress; };仓库源码逐一对应core/brand.ts 第 1 行就是Brand的定义core/field/address.ts 完整实现了FieldAddress与toFieldAddress且校验消息为A field address must be a non-empty path.form/form-store.ts 定义了FormId Brandstring, FormId与toFormId校验消息为A form id must be a non-empty path.并让FormValues RecordFieldAddress, unknown、store 状态与 hooks 全部以FormId/FieldAddress为键——见form-store.test.ts第 392-393 行验证toFormId()抛错与form-store.hooks.test.tsx用toFormId(posts/a.mdx)构造表单;config.ts 用ResolvedConfig BrandComposedConfig, ResolvedConfig经asResolvedConfig收口defineConfig返回该类型。结论正如 AGENTS.md 所述一个FormId不会因为类型错误被当作FieldAddress使用类型系统在编译期就堵住了“字符串互相乱传”的隐患。六、注释与行文ASD-STE100 简化技术英语v4 所有文字性内容——代码注释、_docs/、README——遵循ASD-STE100 简化技术英语本目录的 README.md 是词汇表的参考基准。STE 中最重要的规则主动语态、现在时写 “The bin writes files”不写 “files are written by the bin”一句一个指令或事实句子保持短约 20 词以内一词一义同一事物始终用同一个词——document 就是 document不要随文件不同换成 page、entry 或 record无废话不要 simply、just、note that、in order to。注释政策此前一次清理确立只注释陷阱、不变式与 ADR 指针删除复述代码的注释当某个决策解释了代码时按编号引用 ADR。AGENTS.md给出的两个示例在源码中能找到呼应例如 rpc/proxy.ts 第 1-4 行的注释直接引用 ADR-007 并说明 “types cross through animport type…no server code and no secret reaches the browser”第 29-31 行引用 ADR-023 §4 解释 bearer token 的挂载方式第 52-55 行解释RESERVED_PROXY_KEYS是为了防止await client.media挂起或调试器误发 POST——每一处都指向“代码为何如此”而非复述“代码做了什么”。七、错误处理unknown捕获值与自定义错误类7.1 捕获值一律视为unknown规则捕获的值是unknown绝不假设它是Error。捕获变量按代码库惯例命名为cause用instanceof Error收窄兜底用String(cause)try { await save(document); } catch (cause) { if(cause instanceof Error){ logError(cause.message) }else{ logError(String(cause)) } }这一惯例同样见于 rpc/proxy.tsRpcError通过instanceof区分已知失败。7.2 用自定义错误类区分已知失败规则区分已知失败用自定义错误类而不是匹配错误消息字符串。做法是继承Error并用instanceof检查。AGENTS.md给出的实例if (cause instanceof RequestBodyTooLargeError) { res.statusCode 413; }其中RpcError定义在 src/rpc/proxy.ts携带status与code字段name RpcErrorRequestBodyTooLargeError在local-data-layer.vite.ts中定义。instanceof模式的好处是错误语义与字符串解耦重构提示文案不会破坏上游的类型化判断。八、React / JSX条件渲染禁用手写规则条件渲染不用改用显式null的三元表达式。原因是会把假值如空数组时的0泄漏进 DOM// ❌ Bad — renders 0 when items is empty, leaks falsy values into the DOM {items.length List items{items} /} // ✅ Good {items.length 0 ? List items{items} / : null}这一规则与document-form.tsx中“脏状态徽标”的渲染方式互相呼应——仓库实现里对脏标记的渲染同样遵循显式判断dirty为真才渲染徽标 span避免把假值泄漏到页面。九、无障碍A11y名字从哪来aria-label就用在哪AGENTS.md的无障碍章节是 v4 admin 表单可访问性的精确技术规范共五条且全部能在 admin/document-form.tsx 等源码中找到实现字段行row给字段命名admin/document-form.tsx渲染Label并用htmlFor指向控件字段控件渲染id{address}自身不再有 label。源码第 20-27 行正是如此先查字段注册表里metadata.labelable是否为false再决定htmlFor{labelable ? name : undefined}。绝不在字段控件上放aria-labelaria-label优先级高于label会覆盖作者写的字段名——名为 SEO description 的字段会被读屏器读成seoDesc。因此字段的名字只能来自行row。图标按钮要aria-label工具栏按钮没有label且只显示图标aria-label是正确工具若按钮同时有文字aria-label必须包含该文字WCAG 2.5.3按钮内每个图标用aria-hiddentrue声明为装饰性。htmlFor够不到的控件用aria-labelledbymetadata.labelable: false的描述型控件没有可被 label 指向的输入框此时行row给 label 一个 id控件读取该 id。富文本字段就是典型例子。断言 accessible name而不是 label 文本用getByRole(role, { name })对无对应 role 的控件如datetime-local用toHaveAccessibleName。getByLabelText无法发现“读屏器把aria-label读错”这类缺陷因为它会直接命中错误的aria-label。错误消息携带rolealert表单校验错误必须能被辅助技术即时播报。仓库中 e2e/rich-text-field.spec.ts 与admin/field-labels.test.tsx等测试即是围绕“accessible name 而非 label 文本”这一断言原则展开的用例。十、相关配套文档发布、弃用与集成路径AGENTS.md是 v4 目录的入口规则文件与同目录三份文档配套README.md包地图 发布规则 所有权所有 v4 包由 TinaCMS 核心团队拥有。“CLI 退出管线”的完整论证也在此DEPRECATIONS.md定义deprecate留在 npm、加deprecated字段、停新功能、remove从 monorepo 删除源码、fold能力并入保留包三种状态并给出决策表。关键迁移点包括tinacms变为tinacms/tinacms、tinacms/cli被吸收、tinacms/datalayer折叠为tinacms/tinacms的 store local-content 插件 外部 Level 适配器、tinacms/schema-tools折叠进t助手函数与 codegen 模块用户侧升级只需把package.json里的tinacms换成tinacms/tinacms再按表格改写 importINTEGRATIONS.md列出迁往独立仓库的 provider 包与集成包。十一、实战速查在 v4 目录工作的最小清单读规则先读 AGENTS.md再读 README.md 与tinacms/tinacms/_docs/architecture.md、plugins.md、field-plugins.md、各字段规格跑命令进入对应包目录执行pnpm dev/pnpm test/pnpm types/pnpm build/pnpm codegenpnpm test:e2e仅限tinacms/tinacms验证改动用tinacms/tinacms/playground/在浏览器里手动验证运行时/编辑器改动改动后运行pnpm codegen让tina-lock.json保持最新并提交守边界不触碰 v3 包、不把 Level 适配器迁入仓库、shadcn 组件用pnpm dlx shadcnlatest add component在tinacms/ui/中维护写代码零any、标识符用Brandto*构造器、捕获值命名cause并instanceof收窄、条件渲染用显式null三元、字段命名交给行row而不是aria-label写注释遵守 ASD-STE100主动语态、现在时、短句、一词一义、无填充词只注释陷阱/不变式/ADR 指针按编号引用 ADR如 ADR-007、ADR-016、ADR-023、ADR-024。输出文章 说明以上正文即为完整文章。【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
