Supabase 开发工具栏(Dev Toolbar)PR 审查指南:环境守卫、Flag 覆盖 Cookie 与遥测事件订阅
Supabase 开发工具栏Dev ToolbarPR 审查指南环境守卫、Flag 覆盖 Cookie 与遥测事件订阅【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase本文以 Supabase 仓库中packages/dev-tools/开发工具栏及其在packages/common/中的集成点为对象系统讲解一套面向 PR 的审查清单如何验证构建期 tree-shaking 守卫与运行时IS_LOCAL_DEV双层保护、x-ph-flag-overrides/x-cc-flag-overrides两个 feature flag 覆盖 cookie 的读写与合并优先级、PostHog 客户端遥测事件的订阅链路以及基于 SSE 的服务器遥测流。读完后你可以在改动工具栏或其集成点时逐项核对安全性、类型强转和可见性缺口确保预览与生产环境始终只拿到 stub、覆盖 cookie 永远不污染线上 flag 求值。适用场景与审查边界该工具栏的定位是给内部开发用的「调试面板」在local与staging环境中它把客户端与服务端遥测事件汇聚到一个可拖拽的浮层里并允许通过 cookie 本地覆盖 PostHog 与 ConfigCat 的 feature flag。根据审查清单文档 SKILL.md以下改动路径属于审查范围改动位置审查关注点packages/dev-tools/**工具栏全部实现由supabase/growth-eng在 CODEOWNERS 中负责posthog-client.tsflag 覆盖 cookie 的读取、遥测事件订阅feature-flags.tsx覆盖 cookie 的合并逻辑应用层挂载apps/studio/、apps/www/、apps/docs/中的DevToolbarProvider/DevToolbar/DevToolbarTriggerProvider 增删、apiUrl指向、渲染顺序一个需要特别留意的治理细节posthog-client.ts与feature-flags.tsx不在growth-eng 的 CODEOWNERS 范围内。当前 CODEOWNERS 仅登记了/packages/dev-tools/这一条规则因此只改动上述两个 common 文件的 PR 不会自动请求 growth-eng 审查需要在 PR 流中人工留意。环境守卫构建期与运行时双层保护工具栏使用了两层保护机制这是整个审查清单的第一优先级。第一层构建期 tree-shaking生产安全的主机制入口文件 index.ts 采用条件导出模式// Duplicated for tree-shaking — bundler must see literal process.env reference. // Keep in sync: DevToolbarContext.tsx, DevToolbar.tsx, DevToolbarTrigger.tsx, feature-flags.tsx const env process.env.NEXT_PUBLIC_ENVIRONMENT const isToolbarEnabled env local || env staging export const DevToolbarProvider !isToolbarEnabled ? ({ children }: { children: ReactNode; apiUrl?: string }) children : DevToolbarContextModule.DevToolbarProvider export const useDevToolbar !isToolbarEnabled ? () noopContext : DevToolbarContextModule.useDevToolbar export const DevToolbar !isToolbarEnabled ? (_props: { extraTabs?: import(./types).ExtraTab[] }) null : DevToolbarModule.DevToolbar export const DevToolbarTrigger !isToolbarEnabled ? () null : DevToolbarTriggerModule.DevToolbarTrigger机制要点打包器在构建时内联process.env.NEXT_PUBLIC_ENVIRONMENT的字面值使这些三元表达式变为静态判断。在prod构建中!isToolbarEnabled恒为true于是每个导出都是 stub——DevToolbar与DevToolbarTrigger渲染null、DevToolbarProvider直接透传 children、useDevToolbar返回一个noopContextindex.ts#L17-L26而实现模块整体从 bundle 中被消除。这就是生产环境安全性的主保障。这里有一个必须严格遵守的约定同样的字面量process.env检查在 DevToolbarContext.tsx、DevToolbar.tsx、DevToolbarTrigger.tsx、feature-flags.tsx 中被重复书写。原因是打包器只能看见直接的字面量引用无法追踪从别处导入的常量所以这五处必须人工保持同步。源码注释里也明确写着 Keep in sync。第二层运行时 IS_LOCAL_DEV 守卫在实现模块内部还有一道更细的守卫。以 DevToolbarContext.tsx 为例const env process.env.NEXT_PUBLIC_ENVIRONMENT const IS_TOOLBAR_ENABLED env local || env staging const IS_LOCAL_DEV env localIS_LOCAL_DEV只放行local环境它门控了两个 local-only 的部分SSE 事件流DevToolbarContext.tsx中连接${apiUrl}/telemetry/stream的 effect 以if (!IS_LOCAL_DEV || !isEnabled || typeof EventSource undefined) return开头DevToolbarContext.tsx#L117-L118即 staging 环境有工具栏但没有服务器事件流local-only UIDevToolbar.tsx#L464-L468 在非 local 环境下提示 Server-side events are only visible when using the toolbar in local development。审查检查点守卫被删除或放宽工具栏只允许local与staging启用preview 与 production 构建必须继续拿到 stubindex.ts中 tree-shaking 三元保持完整——它是生产安全的主机制新增的组件或导出绕过既有守卫模式例如直接import实现模块而不走index.ts的条件导出。测试侧已有对应验证DevToolbar.test.tsx 将NEXT_PUBLIC_ENVIRONMENT设为prod后断言「不渲染任何按钮」且「不注册window.devToolbar」local 环境未启用时另有独立用例组。改动守卫逻辑后跑通这些用例是基本验证手段。Feature Flag 覆盖 Cookie写入端、读取端与合并优先级工具栏允许本地覆盖两套 feature flag通过两个 cookie 承载Cookie 名覆盖对象x-ph-flag-overridesPostHog flag 覆盖x-cc-flag-overridesConfigCat flag 覆盖写入端DevToolbar 面板DevToolbar.tsx 中的togglePhFlagOverride/toggleCcFlagOverride负责写入。逻辑分两支回滚分支若parseOverrideValue(value, originalValue)解析后的值与原始值相等valuesAreEqual则从覆盖对象中删除该 flag 并保存即「把值改回原样等于取消覆盖」覆盖分支先把原始值记入 localStorage 的 originalsPH_ORIGINALS_KEYdevToolbarFlagOriginals:posthogCC_ORIGINALS_KEYdevToolbarFlagOriginals:configcat定义于 utils.ts#L30-L31再写入 cookie。覆盖值为空对象时直接deleteCookieDevToolbar.tsx#L238-L244「Reset Reload」按钮会删除两个 cookie、清空 originals 并window.location.reload()DevToolbar.tsx#L328-L338。读取端一posthog-client.ts 的 getFeatureFlagposthog-client.ts#L321-L337 在查询 SDK 之前先查 PostHog 覆盖 cookiegetFeatureFlag(key: string): string | boolean | undefined { if (typeof document undefined) return undefined if (process.env.NODE_ENV development) { try { const cookieEntry document.cookie .split(;) .map((c) c.trim()) .find((c) c.startsWith(x-ph-flag-overrides)) if (cookieEntry) { const overrides JSON.parse( decodeURIComponent(cookieEntry.substring(x-ph-flag-overrides.length)) ) if (key in overrides) return overrides[key] } } catch {} } if (!this.initialized) return undefined // ... 回落到 posthog.getFeatureFlag(key) }注意这里的守卫是process.env.NODE_ENV development——只有本地开发构建的运行时才会读 cookie线上构建这段代码虽然存在但永不命中配合isToolbarEnabled构建期判断覆盖 cookie 不可能影响生产 flag 求值。读取端二feature-flags.tsx 的合并逻辑feature-flags.tsx#L176-L226 在初始化时把覆盖 cookie 合并进 flag store优先级规则是清单文档中明确要求核对的部分// Process PostHog flags if loaded if (Object.keys(flags).length 0) { if (isDevToolsEnabled) { const phOverrides safeParse(cookies[x-ph-flag-overrides]) flagStore.posthog { ...flags, ...phOverrides } } else { flagStore.posthog flags } } // Process ConfigCat flags if loaded const vercelOverrides safeParse(cookies[vercel-flag-overrides]) const ccOverrides isDevToolsEnabled ? safeParse(cookies[x-cc-flag-overrides]) : {} overridesCookieValue { ...vercelOverrides, ...ccOverrides, // local overrides take precedence }即ConfigCat 侧先铺vercel-flag-overrides再让x-cc-flag-overrides覆盖其上开发工具栏覆盖优先且整个合并只在isDevToolsEnabledlocal/staging下才读取x-cc-flag-overridesPostHog 侧则是「原始 flags 展开在前、覆盖展开在后」的浅合并。类型强转parseOverrideValue 与 valuesAreEqual面板里的输入框统一提交字符串靠 utils.ts#L60-L90 做类型还原这是审查中容易埋雷的地方valuesAreEqual任一侧为number时用Number()归一后比较NaN 判不等布尔值走严格其余回落String(a) String(b)。它决定了「改回原值」是否被识别为取消覆盖parseOverrideValue按原始值的类型决定解析方式——原始为number时Number(value)且 NaN 回退原值原始为boolean时字符串true忽略大小写→true其余走Boolean(value)原始为string时直接String(value)。改动这两个函数必须评估类型强转边界例如把布尔判断从toLowerCase() true改成别的写法会让false、空串等输入的行为发生静默变化。审查检查点cookie 名变更必须写入端与所有读取端同步写入方DevToolbar.tsx读取方posthog-client.tsx-ph-flag-overrides与feature-flags.tsx两个feature-flags.tsx合并/优先级逻辑变更当前基线vercel-flag-overrides先、x-cc-flag-overrides在本地开发下覆盖其上覆盖 cookie 出现在IS_LOCAL_DEV/isLocalDevisDevToolsEnabled守卫之外——覆盖永远不能影响生产 flag 求值parseOverrideValue/valuesAreEqual的类型强转回归。遥测事件订阅从 capture 到工具栏的完整链路工具栏的 Events 面板展示客户端遥测链路是posthogClient.subscribeToEvents()→devListeners→DevToolbarContext追加事件。订阅与发射PostHog 客户端维护一个SetClientTelemetryListenerposthog-client.ts#L67通过subscribeToEvents注册、返回取消函数posthog-client.ts#L419-L422。三个 capture 方法在调用posthog.capture/identify成功之后才调用emitToDevListenerscapturePageView→emitToDevListeners(pageview, $pageview, properties)posthog-client.ts#L171-L173capturePageLeave→emitToDevListeners(pageleave, $pageleave, properties)identify→emitToDevListeners(identify, $identify, { userId, ...properties })emitToDevListeners本身有两条防御设计值得在审查中守住posthog-client.ts#L424-L455if (this.devListeners.size 0) return——无监听者时零开销生产环境该 Set 恒为空遍历监听器时每个 listener 单独try/catch一个开发端监听器抛错不能拖垮真实的 capture 路径。工具栏侧在 DevToolbarContext.tsx#L99-L115 中于isEnabled后订阅把ClientTelemetryEvent打上source: client标签后经appendEvent入队appendEvent以source-id去重并按MAX_EVENTS 200上限截断DevToolbarContext.tsx#L26, L91-L97。已知可见性缺口captureExperimentExposure直接调用posthog.capture()不经过emitToDevListenersposthog-client.ts#L402-L417因此实验曝光事件在工具栏中不可见——这是设计现状审查时不要把它当 bug 修但新增方法时应意识到这条模式。审查检查点对emitToDevListeners/subscribeToEvents的改动不能给真实 capture 路径引入副作用抛错、阻塞、篡改事件数据devListeners同步遍历的方式是否会延迟事件派发新增的 PostHog 客户端方法如果 capture 事件但没调用emitToDevListeners会造成工具栏可见性缺口需要在 PR 中明确取舍。SSE 服务器遥测流URL、session_id 与指数退避local 环境下工具栏还会通过 Server-Sent Events 拉取服务端遥测。连接逻辑集中在 DevToolbarContext.tsx#L117-L187const sessionId getCookie(session_id) const streamUrl ${ensurePlatformSuffix(apiUrl)}/telemetry/stream${ sessionId ? ?session_id${encodeURIComponent(sessionId)} : } eventSource new EventSource(streamUrl, { withCredentials: true })配套的重连参数DevToolbarContext.tsx#L29-L31常量值含义SSE_INITIAL_RETRY_MS1000首次重连延迟SSE_MAX_RETRY_MS30000延迟上限SSE_BACKOFF_MULTIPLIER2指数退避倍数行为特征onopen成功即把延迟重置回 1000msonerror时关闭当前连接、按当前延迟setTimeout重连并把延迟min(delay * 2, 30000)翻倍组件卸载时同时清理EventSource与 pending 的 retry timeout防止连接泄漏。服务端事件解析为ServerTelemetryEvent含sessionId、eventType: capture | identify | groupIdentify | alias见 types.ts#L13-L22打上source: server后与客户端事件共用同一事件队列。审查检查点SSE 端点 URL 或session_idcookie 的处理变更——session_id决定了服务端事件与当前浏览会话的关联重连逻辑变更是否引入过度重试或连接泄漏核对 onerror 清理、卸载清理两处跨仓库注意/telemetry/stream端点本身在 platform 仓库中实现改动它需要跨仓库协同审查本仓库内改 URL 契约时务必同步。应用层挂载Provider、面板与 Trigger 按钮工具栏由三部分组成挂载位置分散在三个应用中Provider 面板DevToolbarProvider/DevToolbarapps/studio/pages/_app.tsx#L211-L231legacy pages 路由以及 apps/studio/routes/__root.tsx#L385-L408TANStack 路由根均传入apiUrl{API_URL}apps/www/pages/_app.tsx#L103 与 apps/www/app/providers.tsx#L30两套路由体系各挂一处apps/docs/features/app.providers.tsx#L24。Trigger 按钮DevToolbarTrigger清单文档指出它渲染在各应用的导航/头部组件中。从当前源码结构看www 在 Nav/index.tsx#L159、docs 在 TopNavBar.tsx#L49studio 的 Trigger 则与 Provider 同处一个渲染块routes/__root.tsx/pages/_app.tsx审查时以实际挂载点为准逐一核对。两个补充细节studio 通过DevToolbar的extraTabs属性注入自定义标签页types.ts#L28-L32 定义的ExtraTab这是工具栏对外扩展的唯一 UI 通道启用状态由 localStorage 键dev-telemetry-toolbar-enabled持久化且显式关闭写入false优先于devToolbarDefaultOn这个 ConfigCat flagDevToolbarContext.tsx#L63-L89Provider 还会注册window.devToolbar全局函数供控制台手动开启并在卸载时清理。Trigger 按钮本身支持拖拽吸附到 9 个屏幕位置持久化于dev-telemetry-toolbar-position并显示事件计数角标DevToolbarTrigger.tsx、utils.ts#L92-L104。审查检查点Provider 在某个应用中被添加或移除移除会让该应用的 staging/local 调试能力消失apiUrlprop 变更——必须指向正确的 platform APISSE 与 flag 拉取都依赖它经ensurePlatformSuffix处理后的地址渲染顺序变更可能影响工具栏对 PostHog 上下文的访问例如 Provider 相对FeatureFlagProvider的嵌套关系。不需要 Growth 审查的改动清单文档最后划了一条明确的豁免线纯 UI/UX 的面板内部改动——样式、布局、文案、拖拽行为、popover 定位——不需要 growth eng 审查前提是不触碰上述任一集成点。对照实现可以直观理解这条边界DevToolbar.tsx中的 Sheet/Tabs 结构、EventRow/FlagRow的渲染、DevToolbarTrigger.tsx中的 spring 缓动与吸附计算都属于这一类而index.ts的条件导出、两个 cookie 名、emitToDevListeners调用点、SSE URL 契约则属于越线即需审查的部分。总结审查时的优先级顺序把整套清单压缩成一条审查路径先确认生产安全index.ts的 tree-shaking 三元与五处同步的字面量 env 检查没动、preview/prod 仍拿到 stub再确认覆盖隔离两个 cookie 名在写入端与全部读取端一致、所有 cookie 读取都在isDevToolsEnabled守卫内、parseOverrideValue/valuesAreEqual无类型强转回归然后确认遥测完整性capture 路径无副作用、新 capture 方法是否刻意不进 dev listeners、SSE 重连与session_id语义稳定、跨仓库端点契约变更已协同最后核对挂载面Provider/apiUrl/渲染顺序与DevToolbar.test.tsx的守卫用例。这个顺序保证了任何一次改动如果破坏了「覆盖不进生产、遥测不改业务」这两条底线都会在审查中第一时间暴露。【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考