@bsuite/干-lint (英语)
执行 BSuite 的 ESLint 平面配置插件 一发式建筑 ——每个Supabase实体表都有一个单一的犬类所有者应用,而其他应用可能会读取EAD,但从来没有WRITE加入.
插件运送四项规则:
| 规则 | 默认 | 目的 |
|---|---|---|
bsuite/no-cross-app-write | error | 应用程序调用时出错 .from('<table>').{insert,update,upsert,delete}() 以不同应用程序拥有的表格为对象,使用所有制地图. |
bsuite/no-raw-entity-select | warn | 原始旗帜 .select() 与条形实体表相读取——鼓励通过打入的实体服务层. |
bsuite/no-uuid-input-placeholder | error | 旗帜 <Input placeholder="UUID of …"> 和类似的 UUID 即时占位符 <Input>编号 :<input>编号 :<Textarea>编号 :<textarea>。操作员必须从犬类实体选择器中选择,而不是用手粘贴UUID. |
bsuite/oauth-callback-must-bridge | error | 文件调用时出错 exchangeCodeForTokens() 没有相应的 supabase.auth.setSession() bridge——防止BSU-app交接让用户匿名. |
所有权地图位于 src/ownership-map.json 并且来源于 docs/20260227-dry-one-shot-architecture-v1.04A.md §1 加上V5-V10的研究结果 docs/20260423-cross-app-write-audit-v1.00W.md。 。 。 .
bsuite/no-uuid-input-placeholder
强制操作者使用条形实体选择器而不是自由文本 UUID 输入. 在2026-06-01扫荡后被触发(PRs #933–#939),它取代了16个CRM7页的原始UUID输入.
检测模式
// ❌ flagged — operator should pick, not paste
<Input placeholder="UUID of host employer" />
<Input placeholder="Enter apprentice UUID" />
<Input placeholder="Apprentice UUID" />
护身符
// ✅ pick from a canonical entity selector
import { EmployerSelector } from '@/components/entity/selectors';
<EmployerSelector
value={hostEmployerId}
onSelect={(entity) => setHostEmployerId(entity?.id ?? '')}
/>
如果没有选择器,请创建一个镜像 IncidentSelector I4图案(租户范围通过明确 .eq('tenant_id', …) 在RLS顶部.
允许逃跑
- 测试、规格、故事和
__mocks__路径自动跳过. - 内入
// eslint-disable-next-line bsuite/no-uuid-input-placeholder用于真-无名称-实体表面(开发者唯一的调试工具)。 以评论记录原因.
安装
pnpm add -D @bsuite/dry-lint
该插件为ESLint v9平面配置相容. 它宣布 eslint >=9 作为同伴的依赖.
使用量
建议的单行监听
// eslint.config.ts
import bsuiteDryLint from '@bsuite/dry-lint';
export default [
bsuiteDryLint.configs.recommended, // bsuite/no-cross-app-write: error
// ...rest of your config
];
仅警告(分阶段推出期间有用)
import bsuiteDryLint from '@bsuite/dry-lint';
export default [
bsuiteDryLint.configs.warn, // bsuite/no-cross-app-write: warn
];
手动带选项的接线
import bsuiteDryLint from '@bsuite/dry-lint';
export default [
{
plugins: { bsuite: bsuiteDryLint },
rules: {
'bsuite/no-cross-app-write': [
'error',
{
// Override the auto-detected app (default: inferred from file path)
appOverride: 'crm7',
// Also report writes to tables not in the ownership map (default: false)
warnOnUnknownTable: true,
},
],
},
},
];
应用检测方法
规则需要知道一个文件属于哪个应用程序,这样它就可以与所有者地图进行比较. 每个真正的应用配置都通过一个明确的 appOverride (参见以上"图解"示例——每个app的 eslint.config.js 套装 { appOverride: '<app>' }因此,在实践中 appOverride 是在生产中实际决定应用程序的;下面的自动探测器只在 appOverride 省略.
侦测( E)detectAppFromPath(), (中文(简体) ), src/app-detection.ts从被子上走 文件自己的目录 寻找最近的 package.json,在第一个 .git 标记(文件或目录——覆盖 git-worktree 校对,在哪里) .git 是文件指向真正的gitdir。 这个 package.json name 字段与 :
package.json name | App 密钥 |
|---|---|
business-suite-unified | bsu |
crm7-complete, (中文(简体) ), crm7 | crm7 |
conduit | conduit |
braden-app | braden |
r80-calculator, (中文(简体) ), r80.3 | r80 |
throughput | throughput |
任意 @bsuite/* 范围软件包 | shared |
若无 package.json 于 .git 边界,或边界 name 不符合,该文件中的规则无操作(未关闭——缺失的检测总是安全的;虚假的归属不是).
bsuite # 1623: (英语) 这个探测器的早期版本与路径相匹配 SEGMENTS 代替(例如处理任意 crm7 目录名称段作为 CRM7 应用程序). 月台标准破了 git worktree add /home/<user>/Desktop/Dev/<app>-<feature>-worktree 公约——工作树被检查为 bsuite/ 与目录名称不字面等同的应用程序名称——并且错误地将此类文件分配给任何已知的应用程序名称偶然出现在路径的任何地方(包括一个贡献者的自家目录). 这个 package.json-基于上面的行走是免疫的,可以完全命名退出目录.
您可以通过 appOverride 选项 —— 和每个运出的应用程序配置 .
如何写入检测
规则走 AST 以任何调用表达式,如果被调用者是一个成员访问,结束于其中之一 insert, (中文(简体) ), update, (中文(简体) ), upsert,或 delete。然后沿着查询-构建器链走回,直到找到最接近的 .from('<literal>') 调用并解决表格名称.
链走柄:
- 直接通话..
supabase.from('x').insert(...) - 链式查询-构建器呼叫 -
supabase.from('x').select('*').eq('id', 1).update(...) await-接通了电话- 在打电话之前,先拨打中间电话
.from()- 不相关的查询方法
它没有报告:
- 读取(
.select(...)(中文(简体) ) - 调用表格名称为非文体表达式(例如变量)的地方。 这是有意的——我们宁可错过几起病例,也不愿错过垃圾邮件假阳性.
- 调用任何已知应用程序外的文件 .
在所有者地图上添加新表格
每个条目都使用 完全一个 联 合 国 owner (单行体书写表面)或 writers (多作者共同拥有, @bsuite/[email protected]+. 设置两者都是一个计划错误.
单一所有者条目(最常见的)
-
打开
src/ownership-map.json。 。 。 . -
添加条目
tables数字 :{ "tables": { "your_new_table": { "owner": "crm7", // one of: bsu, crm7, conduit, braden, r80, throughput, shared, all "readers": ["bsu"], // apps that may .select() but never write "$comment": "Optional context, e.g. audit reference." } } } -
使用
"shared"如果该表由2+应用程序共同拥有,且有文件记载的审计拖拉/仅附加模式(例如.wage_calculation_snapshots) (中文(简体) ). -
使用
"all"用于清晰的事件沉入表(例如.bi_metrics) (中文(简体) ).
多写器条目(PHASE-3c,v0.2.0+)
对于由明确允许的应用程序列表共同拥有的管理员-CRUD表格(例如. tenants + 键 user_tenants,其中BSU是平台所有人和CRM7的租户-管理边缘功能在RLS下执行合法的Org-admin操作:
{
"tables": {
"your_co_owned_table": {
"writers": ["bsu", "crm7"], // explicit allow-list — at least one app
"readers": ["r80", "conduit"], // apps that may .select() only
"$comment": "Why this is multi-writer rather than single-owner."
}
}
}
该规则会通过任何上市的应用程序的书写和旗下非上市的应用程序的书写 crossAppWriteMultiWriter 留言.
版本
弹出 @bsuite/dry-lint 版本(patch for new tables, small for owners moves 或 schema 添加)并重用.
所有者密钥
| 密钥 | 应用 | 还原 |
|---|---|---|
bsu | 商业统一 | business-suite-unified/ |
crm7 | 风险管理7 | crm7/ |
conduit | 管道(招募苯丙胺类兴奋剂) | conduit/ |
braden | braden.com.au 公司网站 | braden/ |
r80 | R80.3 工资计算器 | R80.3/ |
throughput | 吞吐量(理想管理) | throughput/ |
shared | 由多应用程序共同拥有(审计-拖车/仅附件) | 无 |
all | 事件槽——任何应用程序都可以写入 | 无 |
已知的违反行为和执法态势
消费者应用软件应该有线 bsuite/no-cross-app-write 作为 error. 先前存在
遗留的写入路径必须用狭义的每个文件覆盖来隔离
消费者ESLint配置;在警告-模式中不要保留基础规则.
2026-04-27 第2D阶段晋级移动了BSU,CRM7,Conduit,R80.3,Braden,和 在保留有文件记载的例外的同时对错误级执行的流量 用于第五阶段所有权迁移工作.
在所有者映射编辑后运行干运行的帮助以识别任何例外列表 可删除的:
pnpm --filter @bsuite/dry-lint build
node packages/dry-lint/scripts/dry-run.mjs
发展
# Install (use --ignore-workspace until package is added to pnpm-workspace.yaml)
cd packages/dry-lint && pnpm install --ignore-workspace
# Build
pnpm build
# Run tests
pnpm test