资讯动态

Formbricks 仓库工程化指南:基于 AGENTS.md 的 pnpm/turbo 多包仓库协作规范与源码解读

发布时间:2026/9/15 15:44:18 来源:尧图企业网站定制
Formbricks 仓库工程化指南基于 AGENTS.md 的 pnpm/turbo 多包仓库协作规范与源码解读【免费下载链接】formbricksOpen Source Qualtrics Alternative项目地址: https://gitcode.com/GitHub_Trending/fo/formbricks本文是 Formbricks 开源仓库Open Source Qualtrics Alternative的工程化技术指南以仓库根目录的 AGENTS.mdCLAUDE.md 中委派指定的唯一权威工作说明为核心骨架展开。你将了解这个 pnpm/turbo monorepo 的模块划分、开发/测试命令体系、依赖版本治理catalog 机制、构建缓存陷阱stale dist 问题、Tailwind v4 样式作用域、环境变量校验、i18n 工作流、数据库性能约定以及测试分层策略并深入到 pnpm-workspace.yaml、turbo.json、scripts/check-catalog.mjs、apps/web/lib/env.ts 等源码中印证每一个结论。读完本文你将具备在 Formbricks 仓库中独立开发、调试、提交高质量 PR 的完整工程能力。一、仓库总览pnpm/turbo monorepo 的模块组织Formbricks 是一个以 pnpm Turborepo 驱动的 monorepo根 package.json 声明packageManager: pnpm11.7.0与turbo: 2.9.14整体划分为三类工作区工作区目录职责产品应用apps/webNext.js 产品主面功能模块分布在app/与modules/静态资源在public/与images/端到端测试在apps/web/playwright/组件预览apps/storybook渲染可复用 UI 组件供评审使用遵循 Storybook 规范共享逻辑包packages/*databasePrisma schema/迁移、surveys、js-core、types以及config-eslint/config-prettier/config-typescript等工程预设部署与文档docs、docker、charts/formbricks部署配套、Docker 编排与 Helm Chart工作区 glob 定义在 pnpm-workspace.yaml 的packages字段apps/*、docker、packages/*。单元测试与源码同目录存放命名*.test.ts或置于__tests__内——例如 apps/web/lib/crypto.test.ts、apps/web/lib/env.test.ts 均与其被测源码相邻。二、开发命令体系从安装到 E2E 的完整链路AGENTS.md 列出的命令在根 package.json 中均有对应脚本且全部经由 Turborepo 的任务图调度命令实际脚本说明pnpm install—按 pnpm-lock.yaml 安装工作区依赖pnpm db:up/pnpm db:downdev:setup docker compose -f docker-compose.dev.yml up -d/docker compose -f docker-compose.dev.yml down启停应用依赖的 Docker 服务数据库等pnpm devturbo run dev --parallel并行启动全部 app 与 worker 开发服务器pnpm buildturbo run build为每个包与应用生成生产构建pnpm lintturbo run lint pnpm api:v3:lint pnpm catalog:check共享 ESLint 规则 OpenAPI v3 校验 catalog 一致性检查pnpm format/pnpm format:checkprettier --write ./prettier --check .应用/校验 PrettierCI 运行的是format:checkpnpm test/pnpm test:coverageturbo run test --no-cache/turbo run test:coverage --no-cache执行 Vitest 套件可选覆盖率注意显式禁用了 turbo 缓存pnpm test:e2eplaywright test启动 Playwright 浏览器回归套件pnpm db:migrate:devturbo run db:migrate:dev对开发数据库应用 Prisma 迁移一个关键的工程细节Turbo 只会在定义了对应脚本的包中运行任务其余包会被静默跳过。因此每个packages/*工作区都暴露标准的lint/typecheck/test/test:coverage脚本有编译步骤的还有build并存在刻意豁免config-*仅含配置文件只有clean、types无运行时逻辑无需测试、email/types/vite-plugins以源码形式被消费故无build、apps/storybook按策略不写单元测试其组件由apps/web/playwright中的功能旅程覆盖。三、依赖版本治理pnpm catalog 单源机制3.1 机制与动机Formbricks 用 pnpm catalog 作为共享依赖版本的唯一真源凡是被两个及以上工作区使用的依赖只在 pnpm-workspace.yaml 的catalog:块中固定一次版本各 package.json 引用catalog:而非字面版本号devDependencies: { typescript: catalog:, vitest: catalog: }这样升级共享依赖只需改 catalog 条目一处永远不要改某个package.json。其背后的隐患是nodeLinker: hoisted见 pnpm-workspace.yaml 末尾配置会把依赖提升到扁平的node_modules隐藏版本分裂直到它爆炸——AGENTS.md 记录了真实事故apps/web曾用 redis 4 的类型定义去描述 redis 5 的客户端还有一个包构建在与其他十四个包不同的 Vite 大版本上。单消费者依赖刻意留在自己的package.json中catalog 对单一引用者而言只是无意义的间接层。当前 catalog 中值得注意的固定版本包括typescript: 5.9.3、vite: 7.3.5注释明确说明 Vite 8 因 rolldown 迁移与vitejs/plugin-react的 peer 约束而暂缓、vitest: 4.1.6、react/react-dom: 19.2.6、tailwindcss: 4.3.1、prisma/client: 7.8.0、redis: 5.11.0等。3.2 强制机制check-catalog.mjspnpm lint会运行 scripts/check-catalog.mjs它从pnpm-workspace.yaml的 globs 自行推导工作区列表新增包无需接线执行两条规则工作区不得为 catalog 中已有的依赖声明字面版本typescript: 5.9.4紧挨 catalog 的5.9.3正是 catalog 要防止的漂移被 2 个及以上工作区声明、却不在 catalog 中的依赖必须入 catalog否则规则 1 形同虚设。脚本对peerDependencies的范围range刻意豁免——peer 范围是对消费者的兼容性声明而非安装锁点允许比 catalog 宽松例如 packages/survey-ui/package.json 声明 react^19.0.0而构建针对 19.2.6但精确锁定的 peer 版本不豁免。此外它会校验catalog:引用是否指向真实存在的条目brokenRefs并拒绝用未处理的 glob 形状静默跳过工作区——resolveWorkspaceDirs只支持dir/*与字面目录两种形态遇到其他形态直接抛错因为静默跳过对一个守卫脚本而言是最坏失败模式。pnpm-workspace.yaml还带有一份大型overrides安全钉security pins每条都记录了 CVE/GHSA 编号、load-bearing 消费链与验证日期例如hono: 4.12.34四个 CVE、fast-uri3: 3.1.6四个主机解析漏洞、mysql2: 3.23.1、dompurify: 3.4.13等同时启用minimumReleaseAge: 43203 天发布冷却期配合trustLockfile: false连 lockfile 中已有的条目也会被重新验证与patchedDependencies对better-auth1.7.0与better-auth/oauth-provider1.7.0打补丁见 patches 目录。四、构建缓存陷阱stale dist 问题与强制重建4.1 预编译消费模型formbricks/surveys包是预编译的Vite 构建为 UMD ESM产物被复制到 apps/web/public/js。Next.js 应用从dist/而非源码导入。这意味着修改packages/surveys或其依赖packages/survey-ui、packages/types等后必须重建才能让改动在运行中的应用中生效。AGENTS.md 给出了绕过 Turborepo 激进缓存的标准重建命令rm -rf packages/surveys/dist apps/web/public/js/surveys.* node_modules/.cache/turbo pnpm build --filterformbricks/surveys... --force另外两层缓存也需注意浏览器会缓存public/js/下的 UMD 包surveys.umd.cjs重建后需硬刷新CmdShiftR / CtrlShiftR或经 DevTools 禁用缓存若改动仍未生效重启 Next.js 开发服务器pnpm dev。4.2 分支切换后的静默失效同样的陷阱适用于每一个通过构建产物而非源码消费的工作区包formbricks/ai、formbricks/database、formbricks/i18n-utils都在exportsmap 中指向dist/因此apps/web导入的是构建产物而非src/。git switch、rebase 或 pull 改变了src/但dist/保持原样且没有任何告警。这只有在你绕过 Turborepo 时才会咬人直接在apps/web内运行vitest或tsc、pnpm --filter formbricks/web test、IDE 测试运行器都会跳过任务图。而根部的pnpm test与pnpm typecheck是安全的——turbo.json 中formbricks/web#test与formbricks/web#typecheck都通过dependsOn声明了对formbricks/ai#build、formbricks/database#build等六个构建任务的依赖turbo 会在套件运行前重建它们。这也解释了为何 CI 的单元测试工作流无需自己的 build 步骤。失败症状看起来完全不像 stale buildTypeError: Right-hand side of instanceof is not an object类在src/中而不在dist/中缺少具名导出或dist/整体缺失时出现Failed to resolve entry for package formbricks/…你从未碰过的文件出现tsc或 Vitest 失败。确认诊断的方法是检查一个你认为该包导出的符号是否在构建产物中——src/有、dist/index.js没有即特征信号要选入口真正 re-export 的符号内部辅助函数本来就合法地不出现在dist/index.js中grep -rc MyNewExport packages/pkg/src packages/pkg/dist/index.js使用-r是刻意的packages/ai/src含嵌套目录providers/非递归的src/*.tsglob 会把其中一个子目录里定义的符号误报为两侧都缺失。修复方式是重建依赖图仅依赖、不含 Next 应用本身与.github/workflows/integration-tests.yml在套件运行前执行的命令相同pnpm build --filterformbricks/web^...五、Tailwind v4 与工作区包 CSS 作用域Tailwind v4 从消费方应用自身根部开始探测源Next.js PostCSS 构建是apps/webVite 根是apps/storybook绝不下降进入node_modules——而所有formbricks/*工作区包恰恰链接在那里。因此仅在工作区包内部使用的工具类永远不会进入消费应用的样式表。解决方案是被消费的工作区包自带 CSS而非依赖应用扫描它们formbricks/surveys——预构建包从 apps/web/public/js 提供见上一节formbricks/survey-ui——导出./stylesdist/survey-ui.css作用域限定在#fbjsformbricks/email——完全不携带样式表由react-email/tailwind在渲染时把类编译并内联进邮件 HTML。如果确实要以原始源码形式消费某个工作区包的样式应用必须显式声明该包的源文件——探测在应用自身根部即止。apps/storybook是现成范例它把packages/survey-ui作为原始源码消费其storiesglob 直接加载packages/survey-ui/src/**Vite 用 alias 把包说明符指向同一源码它能工作的原因是 apps/storybook/src/index.css 导入了survey-ui 自己的样式表后者带有覆盖packages/survey-ui/src/**的config与source——这些指令相对 survey-ui 的文件解析所以包声明自己的覆盖范围、应用只需引入。优先让包拥有自己的 globs只有当文件是应用自身的如index.css对.storybook/**和src/**的source时才在应用入口写source。仓库整体是CSS-first 的 Tailwind 配置仅存两份 JS/TS Tailwind 配置packages/survey-ui/tailwind.config.ts 与 packages/surveys/tailwind.config.cjs且都通过包自身样式表中的显式config桥接。不要添加任何没有被config引用的tailwind.config.js——Tailwind v4 不会加载它它会静默腐烂。六、代码风格与命名约定语言栈TypeScript、React、Prisma使用共享 ESLint 预设formbricks/config-eslint与 Prettier 预设110 字符宽、分号、双引号、排序后的 import 分组。缩进与命名两空格缩进React 组件与modules/下文件夹用PascalCase函数/变量用camelCase仅常量用SCREAMING_SNAKE_CASE。Mock 放置放进__mocks__目录以保持 import 顺序稳定。import 顺序由trivago/prettier-plugin-sort-imports设定、CI 经pnpm format:check验证__mocks__导入最先它们携带vi.mock调用然后是server-only、第三方包、formbricks/*、~/*、/*、相对导入。顺序不是口味问题评审中另提顺序会被format:check拒绝。质量门使用 SonarQube 识别代码异味与安全热点React 组件 props 一律标记Readonly如({ children }: ReadonlyMyProps)。七、架构与数据流模式Next.js App Router 位于 apps/web/app含(app)、(auth)等路由组服务位于apps/web/lib功能模块位于apps/web/modules。Server actions 已是遗留模式不要再新增。新后端工作属于/api/v3路由客户端用 TanStack Query 消费服务端数据放在查询缓存中而非镜像进useState或 Jotai。现有 server actions 包装服务调用并一致地返回{ data }或{ error }改动它们时保持该契约。Context provider应防御缺失 provider 的用法并在useEffect内用快照 refs 的清理模式避免 React hooks 警告。缓存约定用 Reactcache()做请求级去重用cache.withCache()或显式 Redis 处理昂贵数据不要用Next.js 的unstable_cache()缓存键必须使用createCacheKey.*工具。八、环境变量治理从 env.ts 到构建门禁8.1 三层读取边界在apps/web中应用代码不得直接读process.env有一条 ESLint 规则强制bootstrap、config、脚本与测试文件豁免豁免清单在 apps/web/eslint.config.mjsNEXT_RUNTIME与NEXT_PHASE任何地方都允许因为它们是 Next.js 注入的、无可校验服务端代码读 apps/web/lib/env.ts或其派生的 lib/constants.ts中的env客户端组件读 apps/web/lib/env-client.ts——文件中唯一被批准的客户端process.env读取只暴露NODE_ENV派生常量IS_PRODUCTION_BUILD/IS_DEVELOPMENT_BUILD其头注释ENG-1685解释了原因lib/env.ts中每个变量都声明在server下t3-oss/env-nextjs会在浏览器中读取 server 变量时抛错且lib/constants.ts标记了import server-onlynext.config.mjs导入lib/env.ts所以无效的配置值会让构建或启动失败而不是等到首个需要它的请求才爆炸。8.2 新增变量三件套新增环境变量意味着同时处理三处在 apps/web/lib/env.ts 的serverschema基于zod的createEnv含针对 AI 提供方配置、Authzed 配置等的细化校验如ZOpenAICompatibleBaseUrl用z.url()加 http(s) refine、AI_MODEL在AI_PROVIDER设置时必填与runtimeEnvmap 中都添加若next.config.mjs也要读它加入 turbo.json 中build任务的env由 apps/web/lib/turbo-build-env.test.ts 强制packages/*下的包无法导入 web app 的 env 模块仍直接读process.env该规则不适用于那里。九、i18n 国际化工作流所有面向用户的文本必须使用react-i18next的t()函数键命名小写 点号嵌套如common.welcome翻译文件位于 apps/web/locales其中en-US.json是唯一真源绝对只增改en-US.json其他语言文件由 Lingo.dev 从 en-US 机器生成禁止手写/翻译/编辑增改 en-US 字符串后运行pnpm i18n根 package.json 中它展开为generate-translationsscan-translations为所有其他 locale 生成翻译并校验键。Lingo.dev 也会在提交时自动从 en-US 翻译。十、日期时间渲染约定所有面向用户的日期时间必须使用共享格式化辅助函数禁止在组件中临时调用date-fns、Intl或toLocale*显示语言环境必须来自应用语言真源user.locale、getLocale()或i18n.resolvedLanguage而不是浏览器默认值或隐式undefinedlocale 行为locale 与时区是两个不同关注点locale 控制格式化时区控制所表示的时钟/日历时刻绝不从 locale 推断时区。若存在产品级时区真源则显式使用否则保留存储值的既有语义、避免引入浏览器相关的转换机器面的值存储、API、导出、集成、日志必须保持稳定且不本地化适用时用 ISO 8601 / UTC。十一、数据库与 Prisma 性能约定多租户所有数据必须按 Organization 或 Environment 限定作用域软删除检查isActive或deletedAt字段正确过滤永远不要在prisma.response.count()中使用skip/offset只用where分离 count 与数据查询并用Promise.all并行执行大数据集优先游标分页按createdAt过滤时包含索引字段如surveyIdcreatedAt。十二、测试分层策略Confidence over Coverage12.1 分层原则Confidence over coverage置信度优先于覆盖率测试行为与结果避免脆弱的实现细节测试在最便宜的、能使其失败的层级证明行为。E2E 测试不是更强的单元测试——它有不同主体旅程journey而非逻辑每一条 E2E 测试都由每个 PR 永远买单Playwright 任务是 PR 门禁的临界路径截至 2026-08 约 13 分钟其中约 6 分钟是 Playwright 步骤本身其余为安装/构建/启动覆盖约 110 个测试、约 30 浏览器分钟其墙钟时间永远不能低于最慢的单条测试。新增之前先权衡——有时正确的答案就是不在这个层级加测试。12.2 层级选择表变更类型建议层级新功能区域或横跨多个表面的旅程一条 happy-path E2E 其逻辑的单元测试业务逻辑、不变量、校验、派生、权限——任何纯逻辑对.ts的单元测试路由的授权、响应形状或查询作用域该路由的单元或集成测试已有 happy-path spec 的功能内的 UI 细节都不写——手动验证并在 PR 中说明横跨多个表面的旅程指类似 调查列表 → 编辑器 → 公开调查 → 响应 这样、行为只在浏览器、调查 bundle 与服务器协同接线后才存在的场景。写新 spec 之前先看 apps/web/playwright 的 spec 文件名清单——那是已覆盖区域的清单。PR 的 Coverage 表中每行用五个词标注层级前三个是可被 reviewer 核验的声明unit (red on main)对旧代码失败证明 bug 存在、unit (mutation)只有破坏修复才失败因为被测代码是新增的、unit (guard)无论如何都通过防范未来回归e2e与manual说明检查在哪里执行。每一行unit/e2e都要指名所依据的测试或 spec行内或Rerun:行裸写pnpm test等于什么都没说。12.3 该做与不该做DoE2EPlaywright每个功能区域一条 spec不是每个 ticket、不是每个组件。默认向该区域现有 specapps/web/playwright下追加断言或test.step新*.spec.ts只留给尚无 spec 的区域并以区域名命名如billing.spec.ts。遵循套件自身模式通过 Prisma 或/api/v3播种状态而非点击生成参考 playwright/utils/accessibility.ts、每条测试一个旅程并用test.step分阶段参考 settings-tags.spec.ts、在功能层断言参考 survey-overview.spec.ts。单元测试覆盖稳定高价值的.ts逻辑validator、transformer、evaluator、计算、边界情形断言放在输入输出上spec 与被测代码同目录utility.test.ts网络与存储边界经formbricks/*的 helpers mock。API v3 契约测试Schemathesis每个被文档化的/api/v3操作在每个 PR 上对着真实实例驱动且必须匹配提交的 OpenAPI bundle状态码、content type、响应 schema——无需为端点注册文档化一个操作即注册它。要对真实数据而非文档化的 403 验证新操作在 packages/database/src/scripts/seed-contract-fixtures.ts 添加资源本地运行说明见 docs/api-v3-reference/contract-tests/README.md。手动 QA尤其发布在 staging 上验证并报 bug关键 bug 回移植并重测。开 PR 前运行pnpm test触及关键流程时运行pnpm test:coverage。合并/收窄/删除一条 E2E spec 是正当工作——像其他变更一样记录在 PR 的 Coverage 表中。Do not不要为.tsx文件写组件或 UI 单元测试这也不是改为写 E2E的指令组件单元测试缺失不产生覆盖义务。若组件含值得证明的逻辑把逻辑提升到.ts模块在那里单测渲染由穿越它的功能旅程附带覆盖。不要对组件做 E2E语言选择器、面包屑、侧边栏链接列表、某 widget 上的 ARIA 属性、某编辑器内的击键、字段校验消息、过滤列表的搜索框——这些都不值得动用浏览器、登录与播种租户。不要构建变体矩阵只覆盖承担风险的单一情形第二个 viewport、主题、locale、角色或布局需要各自陈述理由相邻 spec 这么做了不是理由。调查渲染的可访问性工作扩展既有 axe 门禁survey-accessibility.spec.ts。不要添加覆盖率驱动或低信号测试不要写锁定实现细节、标记、快照或制造 churn 的测试断言一组精确的 nav 标签列表就是 churn 而非覆盖不要创建 mega 或 flaky E2E避免 timing hackwaitForTimeout、slowMo与不稳定依赖。slow只是分诊元数据——playwright.config.ts 与 CI 都不读它打标签不会让成本消失。十三、PR 与提交规范模板即真源提交遵循轻量 Conventional Commitfix:、chore:、feat:通常附带 PR 号例如fix: update OpenAPI schema (#6617)保持提交聚焦且 lint 干净。每个 PR 必须使用 .github/pull_request_template.md 并遵循其内联指引——模板是 PR 结构的真源。ticket 行是唯一允许 magic wordFixes/Ref/Closes与 ticket id 并列的位置Linear 与 GitHub 会扫描全文同样的词组写在正文里即使在反引号内同样会链接并关闭该 ticket。PR 描述是被阅读的不是被归档的正文details折叠外控制在 350 词以内、一屏列表最多三个 bullet、每个最多二十词Coverage 表最多六行。## What why以Was:/Now:成对开篇一句讲之前如何表现、一句讲现在如何用户可见效果在前、机制在后。## Where to look链接一至三处承担风险的位置。装不下的细节进折叠而非丢弃。## Breaking changes下的复选框是你自己的决定不是形式按模板的破坏性变更清单判断 diff适用即勾选- [x]不适用留空纯粹增量变更、以及本仓库内部无外部消费者触及的内容不算破坏。它是breaking-changelabel 的唯一输入后者驱动 release notes 与自托管迁移指南——答案错误要么凭空造出迁移条目、要么藏起一条。每次 diff 增长都重新检查该复选框pr-label-sync.yml只读复选框所以勾选下方的文字无法改变 label——CodeRabbit 的Breaking changes match the diff检查会把勾选与 diff 比对勾选后需为每条破坏性变更写文档。不要复述 CI 已报告的lint、typecheck、单元测试、构建、Sonar——描述承载这些检查无法展示的内容。其他协作约定Next.js 工作不依赖训练数据任何 Next.js 相关工作路由、布局、服务端/客户端组件、缓存、next.config 等应使用nextjs-docs技能它索引版本固定的本地文档.next-docs/。Agent 设置共享 agent 技能与子代理安装在.claude/设计上下文在.agents/构建或评审 UI 前先读.agents/formbricks-context/DESIGN.md若存在。文档apps/docsMDX 顶部加title/description/iconfrontmatter不以 H1 开头用 Camel Case 标题Enterprise 专属内容加 Enterprise 提示块。Storybookstories 放组件目录的stories.tsx并从./index导入使用storybook/react-viteargTypes 分Behavior/Appearance/Content包含 Default、Disabled、WithIcon、全部变体与边界情形。GitHub ActionsGITHUB_TOKEN设置最小permissionsubuntu-latest上以step-security/harden-runner为首步。十四、质量检查清单保持代码 DRY 且精简删除死代码与未用导入遵循 React hooks 规则effect 聚焦避免不必要的useMemo/useCallback优先类型推断避免any使用formbricks/types的共享类型。这被强制typescript-eslint/no-explicit-any在packages/*是 error、在apps/web是 warningtypescript-eslint 基线正按规则逐个收紧见 ENG-2264。绝不新增any——今天的 warning 会在其规则积压清空后变成 error组件保持聚焦避免深层嵌套确保基本可访问性。十五、速查表常用命令与关键文件常用命令pnpm install # 安装工作区依赖由 pnpm-lock.yaml 锁定 pnpm db:up # 启动数据库等 Docker 服务 pnpm dev # 并行启动全部开发服务器 pnpm build # 生产构建全部包与应用 pnpm lint # ESLint OpenAPI v3 lint catalog 检查 pnpm format # 应用 Prettier pnpm format:check # 校验 PrettierCI 运行项 pnpm test # Vitest 套件禁缓存 pnpm test:coverage # Vitest 覆盖率 pnpm test:e2e # Playwright 浏览器回归 pnpm db:migrate:dev # 应用 Prisma 迁移 pnpm i18n # 生成全部 locale 翻译并校验键 pnpm build --filterformbricks/surveys... --force # 调查包强制重建 pnpm build --filterformbricks/web^... # 仅重建 web 的依赖图关键文件索引AGENTS.md —— 仓库唯一权威工作说明CLAUDE.md 委派指向pnpm-workspace.yaml —— 工作区 globs、catalog、overrides、patchedDependencies、linker 配置scripts/check-catalog.mjs —— catalog 一致性强制脚本turbo.json —— 任务图、dependsOn、缓存与 env 声明apps/web/lib/env.ts —— 服务端环境变量校验 schemaapps/web/lib/env-client.ts —— 客户端唯一 sanctioned 读取apps/web/locales/en-US.json —— i18n 唯一真源apps/web/playwright —— E2E spec 清单.github/pull_request_template.md —— PR 结构真源【免费下载链接】formbricksOpen Source Qualtrics Alternative项目地址: https://gitcode.com/GitHub_Trending/fo/formbricks创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价