news 2026/9/8 20:11:22

Supabase enabled-features 特性开关体系:从静态 JSON 到 Studio 自托管运行时覆盖的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Supabase enabled-features 特性开关体系:从静态 JSON 到 Studio 自托管运行时覆盖的完整指南

Supabase enabled-features 特性开关体系:从静态 JSON 到 Studio 自托管运行时覆盖的完整指南

【免费下载链接】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

本指南围绕仓库中 enabled-features 包文档,系统讲解 Supabase 跨 Studio、docs、www 三大前端统一使用的静态特性开关机制:单一事实来源enabled-features.json的维护方式、isFeatureEnabled调用 API 的两种形态、自托管 Studio 通过ENABLED_FEATURES_*环境变量在容器启动时覆盖开关的规则,以及新增一个开关的完整步骤。读完你既能在业务代码中正确消费特性开关,也能为自托管部署精准裁剪 UI 功能。

定位:一套 JSON 驱动的跨应用特性开关源

在 Supabase 主仓库中,apps/下同时存在 Studio(管理控制台)、docs(文档站)、www(官网)等多个前端应用,它们共享大量功能页面与导航入口。为了避免各应用各自硬编码“某功能是否出现”,仓库把特性开关收敛到一个独立包中统一管理。

该包的目录结构与职责如下(packages/common/enabled-features):

文件职责
enabled-features.json特性开关的单一事实来源(source of truth),构建期读取
enabled-features.schema.json每个开关 key 的 JSON Schema 描述与required校验
index.ts导出isFeatureEnabled()、类型Feature及 camelCase 转换实现
overrides.ts解析ENABLED_FEATURES_*环境变量,输出被运行时禁用的开关列表
overrides.test.ts对上述解析逻辑的 vitest 单测
README.md包级使用文档(本文的主体依据)

包通过 packages/common/index.tsx 以export * from './enabled-features'对外再导出,业务侧既可用common的聚合入口,也可按文档示例直接import ... from 'common/enabled-features'

从架构上看,这套体系是“编译期静态默认值 + 可选运行时禁用列表”的组合:所有开关在 JSON 中以true/false给出构建期默认值;运行时(仅 Studio 自托管场景)可额外叠加“禁用列表”进一步收紧,但不会反转静态默认值。

单一事实来源:enabled-features.json

所有开关都定义在 enabled-features.json 中,文件的第二行"$schema": "./enabled-features.schema.json"声明了配套的 JSON Schema,IDE 可以据此获得补全与校验。

文件中每个 key 采用域:功能(如logs:templates)或域_域:功能(如dashboard_auth:sign_up)的命名风格,按功能域分组。以当前仓库中的真实开关为例:

{ "logs:all": true, "logs:templates": true, "logs:collections": true, "logs:metadata": true, "logs:show_metadata_ip_template": true, "docs:self-hosting": true, "docs:full_platform": true, "branding:large_logo": false, "search:fullIndex": true, "project_settings:custom_domains": true, "project_settings:restart_project": true }

这些开关覆盖的功能面非常广,从 enabled-features.schema.json 的description字段可以归纳出几大类用途:

  • 页面/导航显隐:如logs:all描述为“Enable the logs pages and navigation. Disable to hide all logs pages”,即当自托管环境未配置 Logflare 等日志后端时,可一次性隐藏全部日志页面与导航入口;
  • 局部 UI 元素开关:如profile:show_email控制工具栏是否展示用户邮箱,project_connection:show_orms控制连接弹窗中的 ORMs 标签页;
  • SDK/文档内容裁剪:如sdk:pythonsdk:swiftdocs:auth_flows等,用于决定 docs 站渲染哪些文档章节;
  • 登录方式控制:如dashboard_auth:sign_in_with_githubdashboard_auth:sign_up
  • 灰度中的新功能:默认值刻意关闭,如branding:large_logofalse)。

从当前 JSON 实况看,绝大多数开关默认为true,仅少数新功能默认关闭。关闭项会被 index.ts 收集进“静态禁用集合”,这是后面解析逻辑的起点。

消费开关:isFeatureEnabled 的两种调用形态

使用方通过isFeatureEnabled判断开关,文档给出的基础用法如下:

import { isFeatureEnabled } from 'common/enabled-features' if (isFeatureEnabled('logs:templates')) { // ... } const { logsTemplates, logsMetadata } = isFeatureEnabled(['logs:templates', 'logs:metadata'])

关键设计点:传入单个 key 返回boolean;传入数组则返回一个 camelCase 化的对象,方便解构消费。例如传入'logs:templates',返回对象上的键是logsTemplates

其类型签名在 index.ts 中定义:

export type Feature = Profile['disabled_features'][number] | keyof typeof enabledFeaturesStaticObj function isFeatureEnabled<T extends Feature[]>( features: T, runtimeDisabledFeatures?: Feature[] ): { [key in FeatureToCamelCase<T[number]>]: boolean } function isFeatureEnabled(features: Feature, runtimeDisabledFeatures?: Feature[]): boolean

有两个值得注意的类型级细节:

  1. Feature类型由两部分并集构成——Profile['disabled_features'][number](即平台账号资料中可被远程下发的禁用项,类型来自api-types包的ProfileResponseschema)与enabled-features.json中的全部静态 key。因此仓库内已知的开关 key 与账号资料可下发的禁用 key 都被类型系统覆盖,传入不存在的 key 会报编译错误。
  2. FeatureToCamelCase<S>是模板字符串类型,在类型层面把a_b:c_d计算为aBCd这样的 camelCase 形态,让数组重载返回的对象键具备精确推导。

对应的运行时转换featureToCamelCase实现为:先把:全部替换成_,再按_切分,首词保持小写、其余词首字母大写后拼接:

function featureToCamelCase(feature: Feature) { return feature .replace(/:/g, '_') .split('_') .map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1))) .join('') as FeatureToCamelCase<typeof feature> }

例如logs:show_metadata_ip_templatelogsShowMetadataIpTemplatebranding:large_logobrandingLargeLogo。注意首段的 snake_case 也会被转换,因此返回对象键与原始 key 并不一一对应为“去冒号”,使用时以解构出的 camelCase 属性为准。

isFeatureEnabled的第二个可选参数runtimeDisabledFeatures?: Feature[]用于叠加运行时禁用项(例如来自登录账号资料),其判定逻辑是把“运行时禁用列表”与“JSON 中为false的静态禁用列表”合并成一个Set,只要 key 在集合中即为关闭:

const disabledFeatures = new Set([ ...(runtimeDisabledFeatures ?? []), ...disabledFeaturesStaticArray, ]) function checkFeature(feature: Feature, features: Set<Feature>) { return !features.has(feature) }

数组形态则对每个 key 逐个计算并组装成 camelCase 对象。从中可以确认一个约束:运行时覆盖只能“关闭”静态为true的开关,无法“打开”静态为false的开关,因为机制本身只维护禁用集合。

Studio 侧:useIsFeatureEnabled 的分层聚合

文档特别指出:在 Studio 中应优先使用useIsFeatureEnabled(位于 apps/studio/hooks/misc/useIsFeatureEnabled.ts),而不是直接调用裸函数。原因在于该 Hook 在静态 JSON 之上,又叠加了两层来源:

const { profile } = useProfile() const { data: override } = useEnabledFeaturesOverrideQuery() const disabledFeatures = [ ...(profile?.disabled_features ?? []), ...((override?.disabled_features ?? []) as Feature[]), ]
  • useProfile()读取认证账号资料(对应平台侧/platform/profile接口返回的disabled_features);
  • useEnabledFeaturesOverrideQuery()(apps/studio/data/misc/enabled-features-override-query.ts)请求/api/enabled-features-overrides这个 Next.js 接口,拿到自托管运行时覆盖的结果。

两者拼接为禁用列表后再调用isFeatureEnabled。这意味着对 React 组件而言,开关的最终状态取决于“JSON 默认值 + 账号级禁用 + 部署级禁用”的叠加。

运行时覆盖:自托管 Studio 的环境变量机制

为什么需要运行时覆盖

自托管部署(self-hosted)的场景下,运维方不希望为了隐藏某个页面而重新构建镜像。因此该包支持在容器启动时通过环境变量按开关禁用功能,无需修改代码、无需重新 build。

环境变量命名规则

前缀为ENABLED_FEATURES_,每个开关对应一个环境变量。命名规则为:将开关 key 大写,并把所有非字母数字字符(:_-)替换为_

ENABLED_FEATURES_LOGS_ALL=false ENABLED_FEATURES_LOGS_TEMPLATES=false ENABLED_FEATURES_BRANDING_LARGE_LOGO=true

该映射规则的底层实现位于 overrides.ts:

function featureKeyToEnvName(feature: string): string { return ENV_PREFIX + feature.toUpperCase().replace(/[^A-Z0-9]/g, '_') }

因此branding:large_logodocs:self-hosting的转换如下:

Feature keyEnv var name
logs:allENABLED_FEATURES_LOGS_ALL
branding:large_logoENABLED_FEATURES_BRANDING_LARGE_LOGO
docs:self-hostingENABLED_FEATURES_DOCS_SELF_HOSTING
logs:show_metadata_ip_templateENABLED_FEATURES_LOGS_SHOW_METADATA_IP_TEMPLATE

值的解析与容错

环境变量取值必须为truefalse(大小写不敏感,且会先trim()去除首尾空白),对应实现:

function parseBooleanEnv(raw: string): boolean | null { const normalized = raw.trim().toLowerCase() if (normalized === 'true') return true if (normalized === 'false') return false return null }

解析逻辑的整体行为(overrides.ts 的getEnabledFeaturesOverrideDisabledList)遵循以下规则:

  1. enabled-features.json中已知 key 为基准,预先构建“预期环境变量名 → 开关 key”的映射表;
  2. 只处理映射表中出现的环境变量:值为false的加入禁用列表;值为true的不做任何事;
  3. 非法取值会被打印警告并忽略[enabled-features] ${envName} must be "true" or "false" (got "..."); ignoring.
  4. 前缀正确但不对应任何已知开关的环境变量同样被警告并忽略(用于拦截拼写错误),例如ENABLED_FEATURES_NOT_A_FEATURE=false会触发 “does not match any known feature” 警告;
  5. ENABLED_FEATURES_OVERRIDE_DISABLE_ALL是保留名,不走上述匹配逻辑,不产生误报;
  6. 空字符串按“未设置”处理,不警告。

这些边界行为都有对应单测覆盖,见 overrides.test.ts(如“accepts booleans case-insensitively and trims whitespace”“warns and ignores non-boolean values”“warns and ignores env vars prefixed but not matching a known feature”)。

为什么映射是“前向只读”的

文档强调该映射是forward-only(已知 key → 期望 env 名)。因为反向推导不唯一:若允许“env 名反推 key”,ENABLED_FEATURES_BRANDING_LARGE_LOGO既可以来自branding:large_logo,也可以来自branding_large:logo。而实现只从已知 key 单向计算期望的 env 名,再拿进程环境去匹配,天然规避了这种 snake_case 与冒号的碰撞歧义。

后端链路:API 路由如何把环境变量暴露给前端

自托管覆盖之所以能生效,关键在一条 Next.js API 路由。自托管 Studio 部署中,apps/studio/pages/api/enabled-features-overrides.ts 在请求时读取服务端环境变量:

if (IS_PLATFORM) { return res.status(200).json({ disabled_features: [] }) } return res.status(200).json({ disabled_features: getEnabledFeaturesOverrideDisabledList(process.env), })

要点有两个:

  • IS_PLATFORM分流:托管平台(hosted)返回空禁用列表,路由形同 no-op;只有自托管(IS_PLATFORM === false)才真正解析环境变量。
  • 服务端专用:这些变量不是NEXT_PUBLIC_*,前端构建产物里不包含,必须在请求时读取process.env。这正是 README 中“runtime override is surfaced through a Next.js API route and React Query”的含义——docs 与 www 不使用这套 React Query/API 路由体系,它们的开关解析停留在构建期读取enabled-features.json

前端侧由useEnabledFeaturesOverrideQuery消费该接口,再经 useIsFeatureEnabled.ts 合并进禁用列表。

最终判定顺序

综合文档说明与源码实现,Studio 运行期某个开关是否开启的解析链如下:

  1. ENABLED_FEATURES_*环境变量(仅自托管:IS_PLATFORM === true时该层为 no-op);其解析得到运行时禁用列表;
  2. profile.disabled_features(来自/platform/profile的认证账号资料);
  3. enabled-features.json的静态值false项进入静态禁用集合);
  4. 默认值(开关开启)。

需要澄清的一点:步骤 1–3 在实现上并不是“取第一个命中值”的覆盖关系,而是把各来源的禁用项合并为集合后统一判定;任何一层声明“禁用”都会关闭该开关,且没有任何一层能反向启用一个被上层关闭或静态为false的开关。

作用域与已知限制

Studio 专属

运行时覆盖仅对 Studio 生效。docs 与 www 不依赖 Next.js API 路由与 React Query,其开关在构建期即固化,无法在容器启动时通过这套环境变量覆盖。

托管平台不受影响

托管平台侧 API 路由返回空禁用列表,托管 Studio 的功能裁剪只来源于认证账号的disabled_features,与自托管环境变量完全解耦。

“闪现未样式”窗口

useEnabledFeaturesOverrideQuery的请求返回之前,组件会先用 JSON 静态值渲染。对于“JSON 中开启、但运行时覆盖要禁用”的开关,会出现短暂先展示后隐藏的窗口。这是 Hook 时序导致的固有表现,设计 UI 时需要知悉。

Support.constants.ts 属于构建期调用点

apps/studio/components/interfaces/Support/Support.constants.ts 在模块加载时直接调用isFeatureEnabled('billing:all'),用其结果构建CATEGORY_OPTIONS,随后展开进 Zod 表单 schema。由于它在模块顶层执行,走的是静态 JSON 路径,无法被运行时覆盖;若要让该开关也支持运行时覆盖,需要一次更大的重构(例如改为惰性求值或接入 Hook)。这是当前体系下一处明确记录在案的边界。

全量禁用开关:给工具链用的逃生舱

针对文档嵌入/搜索索引生成这类“需要一份过滤版页面清单”的工具场景,包提供了服务器专用的一键全禁用变量:

ENABLED_FEATURES_OVERRIDE_DISABLE_ALL=true

该变量会短路所有开关isFeatureEnabled在入口处先检查它,命中即把所有查询的开关判定为false(index.ts 的前置分支)。其注意事项:

  • server-only:不带NEXT_PUBLIC_前缀,只应在服务端进程设置;
  • 优先级高于其他一切来源(文档原话 “short-circuits over everything else”);
  • 它被overrides.ts显式列入RESERVED_ENV_NAMES,避免被“未知 env 名”警告误报,单测也专门覆盖了这一点。

其注释说明了用途:文档嵌入管线用它配合常规搜索索引的同步构建方式,产出经功能裁剪后的过滤索引。

新增一个开关的标准流程

当某个新功能需要接入这套开关体系时,按文档步骤操作即可,且必须同步修改两个文件以保持一致性:

  1. 在 enabled-features.json 中加入 key 并设定期望的默认值(发布中功能建议默认false);
  2. 在 enabled-features.schema.json 中补充该 key 的type: "boolean"与一句人读description并把它加入顶层required数组

schema 的required数组与additionalProperties: false共同构成约束:JSON 里出现的每个开关都必须被 schema 描述,schema 要求的每个开关 JSON 里都必须存在,双方不一致即可被发现。加入后:

  • 类型系统会自动将其纳入Feature联合类型(keyof typeof enabledFeaturesStaticObj),错误的 key 会在编译期报错;
  • overrides.ts的“已知 key → env 名”映射表由 JSON 动态生成,新开关的ENABLED_FEATURES_*变量即刻可用;
  • 各消费方通过isFeatureEnabled或 Studio 的useIsFeatureEnabled接入即可。

总结:一套文件、两层来源、三种消费方式

回到整体设计,可以把它压缩为几个关键记忆点:

  • 一套文件:所有开关以声明式 JSON 维护,schema 保证描述与校验不漂移;
  • 两层来源:静态默认值(构建期)+ 禁用列表(账号资料 / 自托管环境变量,运行期),最终状态是两者的交集——任何一层禁用即禁用;
  • 三种消费方式:通用场景裸调isFeatureEnabled;Studio 组件用useIsFeatureEnabled自动聚合账号与运行时覆盖;docs/www 维持构建期静态解析。

对自托管运维而言,最常用的落地姿势就是在容器启动时写入若干ENABLED_FEATURES_*环境变量,在“不重建镜像”的前提下按部署环境裁剪 Studio 的功能面(例如关闭日志页、隐藏计费与支持入口等),同时利用ENABLED_FEATURES_OVERRIDE_DISABLE_ALL=true为内容索引工具产出全量过滤版本。

进一步阅读可在仓库中查看:包文档、开关全集 JSON、schema 定义、解析与类型实现、环境变量解析、解析单测、Studio 聚合 Hook、API 路由。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 20:10:48

OpenCode 终端 AI 编程工具从安装到 LSP 集成实战指南

大概从今年年初开始&#xff0c;我身边越来越多原本习惯在 IDE 里装 AI 插件的朋友&#xff0c;开始往终端里跑opencode这类 AI 编程 CLI。一开始我也觉得是折腾&#xff0c;直到自己把 OpenCode 接上项目、配完 LSP、用它在远程服务器上改完几个 bug 之后&#xff0c;才明白 C…

作者头像 李华
网站建设 2026/9/8 20:10:21

从下载到跑通第一局:RPCS3 让 PS3 游戏在 PC 上真正能玩

从下载到跑通第一局&#xff1a;RPCS3 让 PS3 游戏在 PC 上真正能玩 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 如果你在 PC 上想玩 PS3 游戏却被"装不上、跑不起来"劝退过&#x…

作者头像 李华
网站建设 2026/9/8 20:09:25

RPCS3 中文补丁:2 种装法与 4 类故障的核对清单

RPCS3 中文补丁&#xff1a;2 种装法与 4 类故障的核对清单 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 游戏里中文全是方块&#xff0c;或者补丁勾选了却毫无反应&#xff1f;RPCS3 中文补丁…

作者头像 李华
网站建设 2026/9/8 20:09:22

res-downloader 快速上手指南:3 步嗅探并批量下载网页里的视频和图片

res-downloader 快速上手指南&#xff1a;3 步嗅探并批量下载网页里的视频和图片 【免费下载链接】res-downloader 视频号、小程序、抖音、快手、小红书、直播流、m3u8、酷狗、QQ音乐等常见网络资源下载! 项目地址: https://gitcode.com/GitHub_Trending/re/res-downloader …

作者头像 李华
网站建设 2026/9/8 20:09:16

RPCS3 PS3模拟器:5分钟从装好到开机,新手完整上手教程

RPCS3 PS3模拟器&#xff1a;5分钟从装好到开机&#xff0c;新手完整上手教程 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 如果你想在 PC 上玩 PS3 游戏&#xff0c;RPCS3 是目前绕不开的选择…

作者头像 李华
网站建设 2026/9/8 20:08:55

如何用 tiny11builder 快速打造轻量版 Windows 11:保姆级精简指南

如何用 tiny11builder 快速打造轻量版 Windows 11&#xff1a;保姆级精简指南 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder tiny11builder 是一组开源 PowerShe…

作者头像 李华