
Civitai Monorepo Bootstrap 交接指南pnpm Workspace 基础设施包抽取的决策、执行与验证【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai本篇技术指南以仓库内 docs/monorepo-bootstrap-handoff.md 会话交接文档为核心骨架完整还原 Civitai 将单体仓库改造为 pnpm Workspace Monorepo 的引导过程包括 616 个纯文件重命名R100的暂存状态、八条经过充分论证的关键决策、影响实现的六类约束、Phase 0–6 的分阶段执行路线以及 Git 重命名检测保护策略。读完本文你将掌握如何在保留主应用全部调用点不变的前提下把全局基础设施Prisma/Postgres、Redis、ClickHouse、Axiom、Telemetry安全抽取为civitai/*工作区包并理解工厂模式、re-export shim、契约与运行时拆分等关键手法的落地细节。一、交接文档的定位为什么需要一份会话交接文档docs/monorepo-bootstrap-handoff.md不是一份面向新人的架构说明而是一份面向接续工作的 Claude 会话的状态交接单。它记录的是某个规划会话产出了当前工作树之后下一个全新会话接手时不需要重新发现的决策与现状。文档开篇即点明其目的This document captures the state and reasoning from the planning session that produced this worktree, so a fresh Claude session can pick up the work without re-discovering decisions.这类文档的价值在于把散落在对话中的隐性结论哪些方案被否决、为什么否决、边界条件是什么显式固化避免后续会话重复决策或做出与既定方向冲突的改动。1.1 交接时的位置状态文档记录的接手起点如下维度状态Worktreec:\Work\model-share-monorepo-bootstrap分支monorepo-bootstrap从main切出HEAD已同步至最新origin/main搬迁前已同步提交状态本分支尚未有任何提交——所有变更处于暂存staged状态等待评审这一状态设计是有意的把纯文件搬迁作为独立于重构改造的提交前置步骤是 Git 重命名检测能够稳定工作的前提详见第六节。1.2 已暂存的内容构成文档建议用git status --short查看实时状态并给出交接时的统计快照616 个重命名R——纯文件移动零内容改动每个文件相似度均为 100%R1003 个新增文档A——位于docs/的规划文档docs/monorepo-conversion-plan.md——完整计划含阶段划分、决策与运维细节docs/monorepo-directory-snapshot.md——转换后目录布局的可共享视图docs/moderator-app-shared-modules.md——面向未来审核员moderator应用的独立依赖分析被转换计划引用但不阻塞本次工作。这些暂存的搬迁实现了阶段 1–5 的文件重定位将基础设施代码移入packages/civitai-{db,redis,clickhouse,axiom,telemetry}/。文档特别提醒此刻构建是有意处于 broken 状态的——计划中描述的 re-export shim 将在后续提交中补齐。这是先搬后改策略的刻意产物不是意外。二、八条关键决策及其论证接手前必读交接文档强调这些决策是在对话中定案的提出替代方案之前先读它们。以下逐条展开并结合仓库实际落地状态验证。2.1 决策 1五个基础包而不是六个最初设想过civitai-schema-common包但被否决。理由很关键唯一会放进去的文件是领域常量——如browsingLevel.constants.tsNSFW 位标志解释、air.tsAIR 标识符格式、通用flags.ts位运算助手。这些不是基础设施而是领域约定因此留在主应用src/shared/中只有当卫星应用真正需要时才重新评估抽取。从当前仓库看该判断得到延续src/shared/constants/下的browsingLevel.constants.ts、model-version-flags.constants.ts、user-flags.constants.ts以及src/shared/utils/air.ts、flags.ts仍留在主应用内见 docs/monorepo-directory-snapshot.md。2.2 决策 2基础包只装基础设施civitai-db、civitai-redis、civitai-clickhouse、civitai-axiom、civitai-telemetry五个包每个只包裹一类运行时基础设施。窄包化而非一个大而全的civitai/data的价值在于未来的纯文本工具应用可以只依赖civitai/db而不必连带拉入 ClickHouse 或 Axiom 的依赖树。2.3 决策 3基础包之间不互相导入这是整个包划分的宪法级规则基础包之间不得存在civitai/*兄弟依赖只允许外部依赖。高层包如未来的civitai-moderator-common可以组合多个基础包但基础包必须保持独立。若两个基础包需要共享常量通过子路径导出如civitai/redis/keys暴露。2026-06-03 修订例外在基础设施包之下增加一层契约/叶子层——civitai/db-schema是纯 schema 生成类型产物无运行时基础设施包可以向下依赖它就像依赖prisma/client。规则仍然禁止一个基础设施包导入另一个基础设施包的运行时——类型/schema 包是更低一层不是平级兄弟。这一规则在当前仓库有可验证的执行机制交接快照提到 Turborepo 之外还引入了 ESLint 规则强制依赖边界阻止基础包互相导入或导入主应用详见 docs/monorepo-directory-snapshot.md 的 Tooling 一节。2.4 决策 4修订Prisma 一分为二——契约包与运行时包最初计划把 Prisma 全部塞进civitai-db2026-06-03 修订为拆成两个包civitai/db-schema承载 Prismaschema、migrations、programmability、生成的 client、enums.ts、models.ts——即契约source-of-truthcivitai/db只保留 Prisma-client运行时createPrismaClients工厂、pgPool工厂、查询辅助并从civitai/db-schema导入生成的 client/类型。为什么拆外部项目civitai-advertising用Kysely驱动 Prisma 生成的 schema一个schema.prisma挂两个生成器运行时是new KyselyDB()直连原生pg从不使用 Prisma client。把契约拆出来可以让未来的应用只选 Kysely 而不拖入 Prisma-client 运行时。prisma-kysely已内置schema 包现在跑第二个生成器prisma-kysely通过子路径civitai/db-schema/kysely产出 KyselyDB类型Kysely 消费者无需再碰 schema 包。仓库落地验证在 packages/civitai-db-schema/prisma/schema.full.prisma 第 10–40 行可以看到四个生成器——clientprisma-client-jspreviewFeatures [metrics]、enumsnode ./scripts/prisma-enum-generator.mjs→../src/enums.ts、typescriptInterfaces→../src/models.ts、kyselyprisma-kysely→../src/kyselyfileName types.tsenumFileName enums.ts另有updatedAtTables生成器node ./scripts/prisma-updated-at-generator.mjs。而 packages/civitai-db/package.json 声明civitai/db-schema: workspace:*依赖且exports仅暴露.与./kysely./src/kysely.ts正是运行时包向下依赖契约包的落地形态。决策 4 明确仍是6 个包与决策 1 否决的civitai-schema-common不同——后者是领域常量前者是 Prisma DB 契约。未来若出现第二套数据库 schema仍然独立成包。完整布局见 docs/monorepo-package-adaptation-plan.md 第 4 节。2.5 决策 5主应用留在仓库根目录对称的apps/main/布局计划中的 Phase 6不在计划内。原因是避免一次冻结周级别的千级文件大搬迁。新应用放在apps/主应用无限期留在根目录。pnpm workspaces 完全支持根目录作为一等成员packages: [., packages/*, apps/*]不对称布局没有功能代价。仓库中的 pnpm-workspace.yaml 验证了这一点packages: - . - packages/* - apps/*2.6 决策 6re-export shim 无限期保留保留旧导入路径~/server/db/client等作为转发到civitai/db的一行 re-export是漂移容忍drift-tolerant方案主应用约 2,500 个调用点永远不需要改动。代价是同一份代码存在两个合法导入路径评审时会有一定的约定噪音收益是零强制 churn。从当前仓库的目录快照看这一策略全面落地src/server/db/client.ts、db-helpers.ts、pgDb.ts、notifDb.ts、datapacketDb.ts、db-lag-helpers.tssrc/server/redis/下的各文件src/server/clickhouse/client.ts、src/server/logging/client.ts、src/shared/utils/prisma/{enums,models}.ts、src/utils/otel-helpers.ts全部变成指向对应civitai/*包的 shim。2.7 决策 7OTEL 自动埋点保持逐应用src/instrumentation.node.ts保留sdk.start()调用含应用专属的 service name只有辅助函数withSpan等和 prom-client 辅助移入civitai/telemetry。原因自动埋点注册必须在进程加载时打补丁Prisma/Redis/HTTP一旦移入包就会失去自动加载行为。从当前仓库看src/instrumentation.node.ts 仍是一个 312 行的完整文件——除了 OTEL SDK 启动还注册了 CPU profiler、事件循环 stall profiler、longtask 检测器、liveness heartbeat、watchdog、Pyroscope、Kysely 客户端构建等主应用专属逻辑。快照文档所述97 行缩到约 3 行、只调用bootstrapOtel({ serviceName: civitai-app })描述的是最小形态当前主应用因叠加了上述专属注册而保持更完整的体积。从源码结构可以推断OTEL SDK 组装逻辑已经下沉到civitai/telemetry其依赖含opentelemetry/sdk-logs、opentelemetry/resources、prom-client见 packages/civitai-telemetry/package.json而emitOtelLog、createOtelLoggerProvider、registerOtelShutdown等来自civitai/telemetry/otel-logs的导入已被主应用直接使用。2.8 决策 8工厂模式取代模块级单例今日的db-helpers.ts导出dbWrite常量并直接读取env。在 monorepo 状态下每个包导出一个createXClients(config)工厂每个应用用自己的 env 和serviceLabel供 prom-client 标签区分应用实例化。主应用调用点不变——薄包装层shim重新暴露同样的名字。三、影响实现的约束清单交接文档列出了六项必须遵守的实现约束其中多数在当前仓库都能找到对应的落地证据。3.1 Prisma client 输出进入包内通过output ../generated/client位于schema.full.prisma将 Prisma client 输出移入包内两个应用都从civitai/db/client导入从不直接导入prisma/client避免名义类型相同但实为两份拷贝的问题。3.2 Dockerfile 需要两处更新交接文档记载当前 Dockerfile 有两条COPY行钉死了 Prisma schema 路径当时位于第 11 行与第 38 行需要改为packages/civitai-db/prisma/...workspace 安装层需在pnpm install前拷贝pnpm-workspace.yaml 所有packages/*/package.json以保持安装层缓存lockfile 与 package.json 很少变动安装层保持热缓存COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ COPY packages/*/package.json ./packages/ COPY apps/*/package.json ./apps/ # 卫星应用出现后 RUN pnpm install --frozen-lockfile3.3 Next.jsoutput: standalone需要 transpilePackagesNext.js 独立输出模式需要给 next.config.mjs 增加transpilePackages: [civitai/*]备选方案outputFileTracingRoot只在包有预构建tsc产物时才需要本项目不做。当前仓库的 next.config.mjs 第 187–205 行已列出完整的transpilePackages数组包含civitai/db-schema、civitai/db、civitai/db-queries、civitai/shared、civitai/buzz、civitai/redis、civitai/clickhouse、civitai/axiom、civitai/flipt、civitai/telemetry、civitai/auth、civitai/notifications、civitai/moderation等全部工作区包。同时注意一个与 monorepo 直接相关的细节serverExternalPackages中显式外置了prisma/client第 224 行其注释解释了原因——若将其打包应用层会得到自己的 Prisma 运行时副本而dbRead/dbWrite经 transpiled 的civitai/db-schema触达持有另一份$queryRaw用instanceof Sql识别模板参数时会因两份拷贝而失败报operator does not exist: integer jsonb。这正是两个包不能产生名义相同但实例不同的类型约束在构建层面的具象体现。3.4 手工迁移不变Civitai 手工应用 Prisma 迁移prisma migrate deploy被 CLAUDE.md 禁止。搬迁不改变这一点——migrations 移到packages/civitai-db/prisma/migrations/仍由人手工执行scripts/prisma-migrate-with-views-workaround.mjs 中的路径引用需要更新。3.5db-helpers.ts存在循环依赖地雷第 7 行从~/server/db/client.ts导入dbWrite。两个文件移入同一包后这会变成包内循环依赖。解法把dbWrite作为参数传入需要的辅助函数或重构两个文件使其互不导入。3.6 HMR 安全的 prom-client 注册db-helpers.ts第 30–35 行有 try/catch 回退用于 HMR 重跑模块时重新注册 Histogram。该模式必须保留——它在测试期间多应用同进程的场景下同样生效。四、规划文档索引接手顺序交接文档要求按顺序阅读三份规划文档后再做决策docs/monorepo-conversion-plan.md——事实来源source of truth阶段划分、全部悬而未决决策的dev:/ai:往返讨论、运维细节Docker、CI、Next standalone。注意其头部有一则历史计划——已修订说明实际落地与原始计划有两处不同——(1) Prisma schema 生成 client enums.ts/models.ts落在独立的civitai/db-schema包带prisma-kysely生成器civitai/db只保留运行时工厂并依赖 db-schema(2) 后来采用了Turborepo尽管原计划标注not Turborepo。当前仓库根目录的 turbo.json 证实了第二点——build/typecheck/lint/test任务均配置dependsOn: [^build]dev关闭缓存并标记persistent: true。docs/monorepo-directory-snapshot.md——转换后状态的可共享目录树视图。该快照确认基础包已落地、首批卫星应用apps/authSvelteKit 登录中枢、apps/moderatorNext.js 脚手架已搭好骨架根目录prisma/已消失db:generate跑四个生成器Prisma client、enums.ts/models.ts接口、prisma-kysely迁移仍手工应用。docs/moderator-app-shared-modules.md——未来 moderator 应用的独立分析相关上下文不阻塞本次工作。五、后续工作Phase 0–6 执行路线交接文档明确指出当前暂存提交只是文件重定位步骤提交后剩余阶段如下Phase 0 — Workspace 引导纯配置无文件改动新增pnpm-workspace.yamlpackages: [., packages/*, apps/*]已落地见 pnpm-workspace.yaml在next.config.mjs增加transpilePackages: [civitai/*]已落地为 5 个新包civitai/db、civitai/redis、civitai/clickhouse、civitai/axiom、civitai/telemetry各建package.jsonname version main各建tsconfig.json复制event-engine-common/tsconfig.json的模式。Phase 1 —civitai/db最重的一步向packages/civitai-db/package.json添加prisma/client、prisma、pg、prom-client更新schema.full.prisma的output ../generated/client更新根目录脚本db:generate、db:migrate等指向新路径将db-helpers.ts重构为createDbClients(config)工厂解开client.ts↔db-helpers.ts循环依赖在src/server/db/client.ts及其他位置编写 re-export shim——用主应用 env 调用createDbClients并重新导出同名绑定主应用既有调用点零改动验证pnpm run typecheck、pnpm run build、pnpm run dev全部通过。从当前仓库落地形态看civitai/db的依赖已是civitai/db-schemaworkspace:*、kysely、pg、prom-client、zod见 packages/civitai-db/package.jsonexports暴露.与./kysely快照还显示包内另有env.tsloadDbEnv()惰性加载、包自持、kysely.tscreateKyselyClients(config)等说明运行时工厂模式已按计划扩展。Phase 2–5 — redis、clickhouse、axiom、telemetry 同模式每步在 Phase 1 建立工厂 shim 模式后基本是机械操作civitai/redis移入src/server/redis/client.ts约 1,105 行、caches.ts约 1,571 行、queues.ts、entity-metric.redis.ts、entity-metric-populate.ts、resource-data.redis.ts、fail-open-log.tsfail-open-log.ts目前直接logToAxiom需改为通过 config 注入 logger使 redis 包不硬依赖civitai/axiom基础包互不导入缓存 key 常量与 TTL 从caches.ts抽到keys.ts经civitai/redis/keys子路径导出只想要 key 的消费者如缓存失效脚本无需实例化 redis client。当前仓库 packages/civitai-redis/package.json 的依赖为redis、msgpackr、lodash-es、slugify、zod、lru-cache确实没有任何civitai/*兄弟依赖。civitai/clickhouse移入src/server/clickhouse/client.ts约 733 行。ClickHouse client 更简单——单 URL无副本路由。工厂签名createClickhouseClient({ url, database, username, password })。当前仓库 packages/civitai-clickhouse/package.json 依赖仅clickhouse/client、dayjs、zod。若查询辅助逻辑在 services 中如事件追踪辅助留在应用层只搬连接层。civitai/axiom移入src/server/logging/client.ts约 58 行axiom、safeError、logToAxiom最小最简的包几乎单文件但作为干净的依赖边界仍有价值。工厂签名createAxiomLogger({ token?, orgId?, datastream?, podName?, echoToStderr? })。当前仓库 packages/civitai-axiom/package.json 依赖仅axiomhq/axiom-node、zod。civitai/telemetryOTEL 必须拆成两部分——(1) 自动埋点注册src/instrumentation.node.ts的sdk.start()及 Prisma/Redis/HTTP 补丁留在各应用内(2)withSpan、span 属性辅助、src/utils/otel-helpers.ts移入包内。包还可导出bootstrapOtel(config)使各应用文件缩成三行bootstrapOtel({ serviceName: civitai-moderator })。prom-client 指标注册src/server/prom/client.ts同理——辅助进包、注册调用留应用。当前仓库 packages/civitai-telemetry/package.json 依赖opentelemetry/api、opentelemetry/api-logs、opentelemetry/resources、opentelemetry/sdk-logs、prom-clientdevDependencies 含opentelemetry/context-async-hooks、opentelemetry/core。Phase 6 — 明确跳过按决策 5 跳过。仅当出现第三个应用、强烈的布局一致性诉求或工具链无法容忍不对称布局时才重议届时动作为git mv src/ apps/main/src/、迁移next.config.mjs、将根package.json改为纯 workspace 壳、从pnpm-workspace.yaml移除根目录、更新 CI且需要一个无活跃特性分支的冻结周。阶段总览来自转换计划Phase包工作量风险0Workspace 引导低低——纯配置1civitai/db高中——Prisma client 路径变更是最棘手一步db-helpers.ts循环依赖待解2civitai/redis中低——文件多但基本机械3civitai/clickhouse低低4civitai/axiom低低5civitai/telemetry中中——拆分自动埋点与辅助函数需谨慎6主应用移入apps/main/—不在计划内——为规避冻结周成本无限期留在根目录六、Commit 形态与 Git 历史保护6.1 提交切分方案用户偏好待定交接时用户尚未决定提交切分方式文档给出两个合理选项单提交chore: bootstrap monorepo layout (pure file moves planning docs)双提交docs: ...再refactor: move infra files to packages/* (pure rename)——上一个 worktree 使用过可读性更佳两种方式对重命名检测都成立提交前需询问用户。6.2 三条红线不要把 move 和 edit 混进同一个提交。Git 重命名检测的相似度阈值是 50%。若在git mv的同时改内容历史可能无法跟随。当前暂存状态是纯 move务必先提交再做任何重构。不要 squash-merge 迁移 PR。Squash 会把 move/refactor/shim 提交压成一个大 diff提高 Git 漏判重命名的概率。monorepo 转换 PR 应使用rebase-merge 或 merge commit。monorepo-conversion分支是历史产物。上一个 worktree已删除曾在分支monorepo-conversioncommit22d242dee含后来被否决的 schema-common 拆分做过一次重命名检测试验。该分支保留在 origin 上作为试验记录不要试图修复它。6.3 重命名检测的事后验证任何 rebase 之后都要复查git log --stat HEAD~..HEAD应显示R100条目而不是配对的AD。相关工具认知git log --follow path跨重命名显示完整历史普通git log path不行git blame自动跟随简单重命名git blame -C还能检测从其他文件复制来的内容适用于文件拆分文件拆分时 Git 只为内容重叠最高的文件自动判重命名因此优先先 move 后 split而非先 split 后 move。6.4 每文件三提交模式为保证git log --follow与git blame在搬迁后依然可用转换计划给出每文件的经典模式纯 movegit mv src/server/db/db-helpers.ts packages/civitai-db/src/db-helpers.ts零内容改动Git 无歧义判重命名重构改 import、把env换成工厂 config、解开循环依赖小 diff 正确归属到 blame加 shim如适用在旧路径创建一行 re-export它是真正的新文件——原内容的历史已在包路径下。七、快速验证命令集交接文档给出了可直接执行的验证命令这里保留并补充说明# 统计暂存的重命名数量按状态首字母分组 git -C c:/Work/model-share-monorepo-bootstrap status --short | awk {print $1} | sort | uniq -c # 确认重命名相似度为 100% git -C c:/Work/model-share-monorepo-bootstrap diff --cached -M --stat | tail -5 # 抽查某个文件的历史是否跟随搬迁重命名检测抽查 git -C c:/Work/model-share-monorepo-bootstrap log --follow --oneline -5 -- packages/civitai-db/src/db-helpers.ts # 确认 worktree 处于最新 main git -C c:/Work/model-share-monorepo-bootstrap fetch origin main git -C c:/Work/model-share-monorepo-bootstrap log --oneline HEAD..origin/main第一条命令的预期输出为616 R与3 A与文档记载一致第二条若显示R100而非R0xx说明搬迁是纯 move第三条若在包路径下能看到旧历史提交说明重命名检测工作正常。八、落地现状与交接文档的对照将交接文档与当前仓库对照可以确认大部分计划已经落地、少数环节发生了演进Workspace 骨架pnpm-workspace.yaml、next.config.mjs的transpilePackages、turbo.json均就位packages/下实际已有 18 个civitai/*包在五个基础包之外又新增了civitai-auth、civitai-brand、civitai-buzz、civitai-db-queries、civitai-email、civitai-flipt、civitai-mod-utils、civitai-moderation、civitai-notifications、civitai-shared、civitai-storage、civitai-ui等卫星应用apps/auth与apps/moderator也已存在说明 monorepo 生态在基础包之上持续扩张。契约/运行时拆分civitai/db-schema持有四个生成器的 Prisma schema 并暴露./enums、./models、./kysely、./kysely/updated-at-tables、./schema-drift子路径civitai/db只保留运行时工厂并依赖workspace:*的 db-schema——决策 4 的修订版完全落地。shim 策略快照文档逐文件列出的 shim 布局src/server/db/*、src/server/redis/*、src/server/clickhouse/client.ts、src/server/logging/client.ts等是主应用调用点零改动这一承诺的实现基础。OTEL 拆分主应用 src/instrumentation.node.ts 仍承担自动埋点注册与大量应用专属注册辅助能力emitOtelLog等已从civitai/telemetry/otel-logs导入印证自动埋点留应用、辅助进包的决策 7。手工迁移根目录prisma/已不存在schema 与 migrations 全部位于packages/civitai-db-schema/prisma/迁移仍按 CLAUDE.md 约定手工应用。对继续推进的开发者而言交接文档仍然是权威入口它以最紧凑的形式回答了我们现在在哪、哪些决策不可推翻、下一步做什么、哪些坑不能踩其配套的 docs/monorepo-conversion-plan.md 与 docs/monorepo-directory-snapshot.md 则分别提供完整决策史与当前结构地图。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考