
OpenMetadata UI 代码质量门禁从“新增代码必须干净”到 SonarCloud Clean-as-You-Code 的完整落地实践【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata本文讲解 OpenMetadata 如何在前端openmetadata-ui的每一个 PR 上强制执行代码质量以及你还需要在 SonarCloud 和分支保护中完成哪些一次性配置。读完本文你将掌握一套“生成时 评审时”双层质量门禁的完整方案本地一条make ui-checkstyle-changed命令如何与 CI 完全对齐、每条门禁的作用范围与失败条件如何划定、以及如何用 SonarCloud 的 Clean-as-You-Code 模型只对 PR 新增行做卡口使历史债务永远不会阻塞合并。一、治理原则新代码必须干净存量债务渐进偿还整套门禁的第一性原理是新增代码必须干净存量债务逐步偿还。下面列出的每一条门禁都只作用于变更新增的部分而不是整个文件、更不是整个仓库——这样待办清单backlog永远不可能阻塞一个 PR。这一原则直接决定了三个工程决策检查只报告“diff 里出现了什么”而不是“文件里有什么”严重等级error/warn由实测存量决定而非个人口味门禁的落点是ui-checkstyleCI 作业而不是 pre-commit 钩子。原始设计文档见 docs/ui-code-quality-gate.md本文在此基础上结合仓库源码逐层展开。二、两侧结构生成时与评审时各跑一遍由于几乎全新的 UI 代码是 AI 生成的只在 CI 中运行的检查来得太晚——模型在写代码时就已经选错了模式。因此每条规则都存在两份生成时Agent 正在写代码评审时CI知识.claude/rules/*.md由paths:glob 自动加载—强制执行.claude/settings.json的 hooksui-checkstyle作业自助运行make ui-checkstyle-changedrequired status check仓库中这些规则文件确实存在例如 .claude/rules/component-library.md、.claude/rules/frontend-a11y.md、.claude/rules/frontend-performance.md 等由 IDE Agent 按文件 glob 自动加载。一套工具链三个调用点同一份 ESLint 配置在三个地方运行Agent hook、make ui-checkstyle-changed、ui-checkstyleCI 作业。设计上的偏好是能用现成的 ESLint 插件就不要写定制脚本插件匹配的是 AST 而不是 diff 文本、能在编辑器里实时反馈、还能与--fix和eslint-disable组合。只有当一条规则确实无法用 ESLint 表达时才写脚本——tw-guardTailwind/antd 迁移守卫就是这种情况因为它对应的 antd/.less存量864 个和 449 个文件使得“只查新增行”的作用域划定不可避免。其实现位于 scripts/tw-deprecation-guard.js 与 scripts/tw-audit.js。规则背后的深度知识存放在skills/vendor/下的react-best-practices、web-design-guidelines与composition-patterns自 vercel-labs 的 agent-skills 仓库以 MIT 许可原样引入无需安装步骤。Skill 只在被调用时才加载因此把“承重”子集蒸馏进了.claude/rules/frontend-performance.md和frontend-a11y.md在匹配文件上自动加载。Checkstyle 是强制执行点pre-commit 不是新门禁刻意不放进.pre-commit-config.yaml那里的每一个钩子都会在每次 commit 时付出代价而 commit 必须保持快速。ui-checkstyle是门禁唯一必须守住的地方而make ui-checkstyle-changed让你在 push 之前就在本地拿到同样的答案。三、本地运行make ui-checkstyle-changed到底做了什么make ui-checkstyle-changed # 与 CI 完全一致只跑你改动的文件这是唯一需要信任的命令——它既运行修复步骤organize-imports、eslint、prettier、license 头、i18n 同步、app-docs 生成又运行审计门禁tw-audit、tw-guard。门禁是收集式的而非短路式的一个失败不会掩盖其他失败。底层脚本的实现细节Makefile 目标定义见 Makefileui-checkstyle-changed依次在openmetadata-ui/src/main/resources/ui与openmetadata-ui-core-components两个前端工程中执行yarn ui-checkstyle:changed。而 package.json 中ui-checkstyle:changed指向 scripts/ui-checkstyle-changed.sh该脚本的关键行为值得逐条说明基准解析BASE无条件先解析origin/main与 HEAD 的merge-base作为 diff 基准若本地没有origin/main会尝试fetch --depth1再失败则回退到HEAD~1。之所以无条件解析是因为后面的“禁止新增债务”守卫要对 BASE 做 diff不能放进分支逻辑里。变更文件集合git diff --name-only --diff-filterACM $BASE HEAD过滤出openmetadata-ui/src/main/resources/ui/src/下新增/拷贝/修改ACM的.ts/.tsx/.js/.jsx/.json文件并排除src/generated/与src/jsons/两个生成物目录。也可以显式传文件覆盖自动探测。修复步骤按类型分发organize-imports-cli只处理 TS/JS不处理 JSON随后对所有变更文件跑lint:base --fix、pretty:base --write、license-header-fix最后无条件跑yarn i18nlocale 同步与yarn generate:app-docs。审计门禁收集失败脚本特意set e把tw-audit只查变更的 TSX 文件与tw-deprecation-guard.js对 BASE diff的失败收集进数组最后统一报✖ ui-checkstyle failed: 列表并exit 1——这正对应文档所说“与 CI 的 continue-on-error 行为保持镜像”避免set -e在第一个失败处就停住、掩盖其余问题。CI 侧的输出位置在 CI 中warning 会出现在 PR 上标题为UI Checkstyle passed — lint findings in changed files的 sticky GitHub Actions 评论里按规则分组列出变更文件的行号、列号与消息。同样的输出也可在Actions → UI Checkstyle → checkstyle → ESLint Prettier Organise Imports (src)步骤中找到。warning 保持非阻塞ESLint error 与格式化差异仍会让ui-checkstyle失败。CI 作业定义见 .github/workflows/ui-checkstyle.yml。四、每条门禁的作用范围与失败条件门禁作用范围失败条件ESLint Prettier organize-imports变更文件输出与提交形态不一致Licence 头变更文件Apache-2.0 头缺失/过期i18n key 同步全部 localelocale 文件与en-us.json不同步tw-audit变更文件硬编码了可映射到设计 token 的 Tailwind 值tw-guard新增行新的antdimport 或新的.less文件jsx-a11yESLint变更文件19 条零存量无障碍规则中任一触发SonarJSESLint变更文件16 条零存量正确性规则中任一触发OpenMetadata 性能ESLint变更文件静态引入路由页、未加守卫的 lazy 组件、无界模块缓存OpenMetadata import 架构ESLint变更文件架构、循环、barrel、请求扇出类 finding仅 warningSonarCloud 质量门禁新代码本 PR 新增行上的复杂度、重复度或新问题五、组件复用是指导不是门禁.claude/rules/component-library.md承载“应该 import 什么而不是手写”的对照表例如用Select而不是div rolelistbox。没有任何 linter 了解这套设计系统所以这张表是指导与人工评审而不是自动检查。历史上曾为这个目的专门写过reuse-audit脚本后来被移除。它糟糕地重新实现了 ESLint 本就做得好的事对原始 diff 行做正则匹配会在data-role、[rolemenu]选择器与注释上产生误报其手写 git 处理逻辑还可能在 diff 加载失败时误报“干净”。它唯一真实的优势——只检查新增行——是为了容忍一个只有16处实例的存量这个规模不足以justify约 430 行定制代码及其独立测试套件。CI 转而强制的是手搓组件至少必须是无障碍的。jsx-a11y会拒绝无效的role、缺少必备aria-*属性的 role、以及不可用的 Tab 顺序。使用组件库组件是满足这些要求的最简单方式。六、两级严重等级由实测数据选定而非口味所有规则都是开启的。严重等级由规则的实测存量决定——因为 ESLint 是按文件而非按新增行报告的一条带着既有违规的error规则会让任何只是触碰了这些文件的 PR 失败。等级含义当前构成error零实测存量——阻塞16 条 SonarJS 19 条 jsx-a11y 3 条 OpenMetadata 性能warn有存量——在编辑器与 CI 输出中可见不阻塞21 条 SonarJS、15 条 jsx-a11y、4 条 React、10 条 OpenMetadata import 规则、react-hooks/exhaustive-deps、i18next/no-literal-string、typescript-eslint/no-non-null-assertion以当前仓库为例全仓0 error、10120 warning分布在 2228 个文件。这些 warning就是被摆上台面的存量——目标是零逐规则达成。从 eslint.config.mjs 可以直接核对16 条零存量 SonarJS 规则以error显式列出如 no-identical-conditions19 条零存量 jsx-a11y 规则同样显式以error配置见 aria-props 至 scope而高存量规则则显式降为 warn例如sonarjs/cognitive-complexity: [warn, 15]存量 85 处与react-hooks/exhaustive-deps: warn596 文件 1693 处——注释里保留了成本数字方便下一个人判断提升代价。一个值得注意的细节i18next/no-literal-string曾带着TODO: re-enable when the plugin supports ESLint 9被禁用。该不兼容已无法复现——它现在运行正常并报告出大量存量因此以warn恢复。仓库约定是不写用户可见的字符串字面量所以它最终应该升到error。七、仓库专属性能规则三条“只在归零后升 error”的规则eslint-rules/openmetadata-performance.mjs 包含三条仅报告reporting-only的规则测试套件由yarn test:eslint-rules运行。三条规则都是在全量src/扫描达到零命中之后才以error启用的no-eager-page-imports作用于src/components/AppRouter/**拒绝路径包含pages/的运行时静态 importtype-only import 仍然合法。从源码实现看规则通过ImportDeclaration节点判断/pages\//路径模式并检查importKind type或全部 specifier 均为 type-only见 openmetadata-performance.mjs这正是“只拒绝运行时依赖”的实现保证。require-suspense-fallback只识别从 React 导入的lazy/React.lazy。可接受的写法包括组件直接传递给或随后传递给从components/AppRouter/withSuspenseFallback导入的批准 helper或者一条从 lazy 绑定到 JSX 渲染/传递路径的局部变量依赖链且该 JSX 位于带显式fallback属性的真实 ReactSuspense边界之下。模块里无关的其他边界不算数。no-unbounded-module-cache检查模块级、带缓存风格名称的Map与Set绑定。一个缓存需要显式的数值或大写命名的大小比较且被守卫的if分支或while循环体必须用delete或clear对同一个绑定做逐出。这三条规则刻意不自动修复引入 loading 边界、选择逐出策略、以及决定哪个路由依赖应保持静态引入都需要运行时上下文才能判断。八、Import 架构与请求扇出十条 warn 级架构规则eslint-rules/openmetadata-imports.mjs 包含十条仅报告的规则均以warn启用见 eslint.config.mjs 的 openmetadata-imports 配置块、不自动修复因此在其实测存量被消化之前不会导致 CI 失败规则报告内容基线 finding / 文件数no-impure-pure-utils*PureUtils中出现 React/JSX 或向上的 UI、状态、页面、hook 或 REST 依赖62 / 23no-lower-layer-page-importspages 与 AppRouter owner 之外的页面 import291 / 271no-cross-page-imports一个页面特性静态 import 另一个页面特性43 / 32no-rest-ui-importsREST client 依赖 components、pages、hooks、context 或 stores55 / 37no-hook-ui-importshooks 依赖 components 或 pages10 / 6no-circular-imports参与循环的运行时 import/re-exporttype-only 忽略295 / 164no-internal-barrel-imports解析到应用内部indexbarrel 的运行时 importtype-only 允许143 / 134no-lodash-default-import从 Lodash 包根做 default 或 namespace import1 / 1no-api-calls-in-iteration循环或map等动态迭代回调中的 REST 调用28 / 21review-sequential-api-calls同一函数中第二个及以后的直接 await REST 调用供依赖评审207 / 105最后一条刻意表述为“review”评审而非错误静态分析无法证明第二个请求是否依赖第一个。合法的顺序保持原样独立请求并行化。晋升路径把某条规则的存量清零、重新测量、再移到error基线数字就写在 eslint.config.mjs 每条规则旁边下一个人可以直接看到代价。请求评审规则在强制化之前需要单独重新评估。刻意仍关闭的规则及原因react/jsx-no-useless-fragment——它会 autofix所以任何严重等级下eslint --fix都会改写文件并使 git-diff 检查硬失败。正确做法是先落地一个一次性的全仓 autofix commit再以error加入。sonarjs/file-header、arrow-function-convention、shorthand-property-grouping、elseif-without-else等——纯风格且与 Prettier 和仓库既有约定冲突。开启它们会制造成千上万没人打算修的 warning贬低每一条其他 warning 的价值。sonarjs/no-reference-error、no-implicit-dependencies——需要本配置未提供的 resolver/global 配置缺了它几乎全是误报。给warn层加规则之前先确认它是否会 autofix。ui-checkstyle会跑eslint --fix并对产生的 git diff 失败所以warn级的 autofix 规则会静默改写文件并硬失败门禁。当前所有warn规则都是fixable: none或仅 suggestions。react-hooks/exhaustive-deps声明了fixable: code但经实测验证在--fix下不会改写依赖数组——这一点双重重要因为自动添加 effect 依赖会改变运行时行为。九、ESLint 里的 SonarJSSonar 的“快速一半”eslint-plugin-sonarjs与每个 UI PR 上已运行的 SonarCloud 分析是同一个引擎、同一套Sxxxx规则 id。编辑器里的 finding 就是 Sonar 会报告的 finding。高存量 SonarJS 规则同时由 SonarCloud阻塞式强制执行其 Clean-as-You-Code 模型把它们限定到新增行——这是 ESLint 从根本上无法表达的作用域。所以cognitive-complexity与no-duplicate-string在本地是 warn在 PR 门禁上则对新代码阻塞。版本纪律同样严格eslint-plugin-sonarjs被精确固定为4.2.0见 package.json。SonarCloud 在服务端按自己的节奏升级分析器且这种漂移是无声的。十、编辑器里的第三个位置SonarQube for IDE这是同一套规则出现的第三个位置也是唯一逐字显示服务端profile 的位置而不是本地近似安装SonarQube for IDE前 SonarLint——支持 VS Code、IntelliJ 等。在Connected Mode下把 workspace 绑定到 SonarCloud组织open-metadata项目open-metadata-ui。Connected Mode 会拉取项目的质量 profile于是编辑器标出的正好是 PR 门禁会标出的内容——包括 ESLint 本地暂缓的高存量规则且针对新代码标记。不用 Connected Mode 时插件使用自己的默认值会与 CI 不一致。要么绑定要么依赖make ui-checkstyle-changed。十一、SonarCloud 配置管理员一次性完成项目open-metadata-ui组织open-metadata由 .github/workflows/yarn-coverage.yml 扫描。质量 Profile创建一个自定义 profile激活规则镜像 eslint.config.mjs 中启用的集合并在两侧显式设置规则参数——不要指望两个默认值恰好一致例如cognitive-complexity阈值两边都设为 15。质量门禁条件全部只针对 New Code门禁名OpenMetadata UI — Clean as You Code设为项目默认门禁。条件New Code操作符值覆盖率小于90.0%问题数大于0已评审安全热点小于100%重复行占比大于3.0%任何针对 Overall Code 的条件—禁止**绝不要给 Overall Code 挂任何条件。**那会从第一天起被遗留债务击穿破坏整个“只看新代码”契约。上表每条条件都只针对 PR 新增或修改的行评估。新代码 90% 覆盖是这里最严的条件——高于 Sonar 默认的 80%而 UI 目前完全没有覆盖率地板jest.config.js 设置了collectCoverageFrom但没有coverageThreshold。初期它会是失败 PR 最多的条件任何新组件、hook 或 util 都需要测试落在同一个 PR 里。这就是本意——新代码被要求达到存量债务不必达到的标准——但它实质改变了 UI PR 的“完成”定义团队应当在门禁开启前被告知而不是从红勾里发现。两个值得知道的机械后果一个只移动或重排格式的 PR 仍可能把这些行登记为新的且未覆盖覆盖率来自sonar.typescript.lcov.reportPathssrc/test/unit/coverage/lcov.info所以若 Jest 运行失败或 lcov 缺失新代码覆盖率会读成 0%门禁失败。修测试运行而不是修门禁。New Code 定义分支场景参考分支 mainmain自身Previous version或 30 天。在项目设置里配置不在门禁上。分支保护在main上把以下三项标为 requiredRequired check强制执行ui-checkstylelint含 SonarJS jsx-a11y、prettier、licence、i18n、tw-audit、tw-guardui-coverageJest 运行已完成ui-sonar-gateClean-as-You-Code 质量门禁含新代码 90% 覆盖要标ui-sonar-gate而不是 SonarCloud 自己的 check。从 workflow 源码可以确认这个机制扫描被dorny/paths-filter与safe to test标签门控见 ui-checkstyle.yml 与 yarn-coverage.yml所以一个不含 UI 变更的 PR 永远不会产生那个 check若直接依赖它会在分支保护下永远卡住而ui-sonar-gate在扫描被合法跳过时会始终运行并通过。门禁结果直接来自扫描器本身PR 扫描传入-Dsonar.qualitygate.waittrue超时 600sSonarCloud 决策、扫描器失败时退出非零见 yarn-coverage.yml 的 sonar 参数ui-sonar-gate作业把这个结果翻译成贡献者看到的 check其needs: [ui-coverage-tests]并读取sonar_gate_status输出。这是受支持的机制——不要重新引入对/api/qualitygates/project_status的轮询那会与异步报告处理竞态并在超时时静默放行。十二、两个需要预期的行为**被修改的行算新代码。**编辑一个混乱遗留文件里的某一行会把该行的问题拉进门禁作用域。这是渐进偿还债务的机制——你清理你碰到的东西——但读起来像是“门禁在我没写的代码上失败了”。事实并非如此那行就在你的 diff 里。**新代码归属需要首次确认。**PR 扫描传入-Dsonar.scm.disabledtruepush 扫描不传新代码归属来自sonar.pullrequest.*参数大概率没问题——但在第一个受门禁的 PR 上检查 Sonar 的New Code标签页是否只显示 diff 而不是整文件。若显示整文件就从 PR 扫描步骤里去掉那个 flag。十三、存量追踪门禁是故意对旧代码盲的门禁按设计看不到旧代码因此它永远不会告诉你债务是否在减少。每月在overall代码上跟踪sqale_index、code_smells与duplicated_lines_densitySonarCloud 的 measures/search_history 度量查询 API组件open-metadata-ui指标含cognitive_complexity,duplicated_lines_density,code_smells,sqale_index,nclocGET measures/search_history ?componentopen-metadata-ui metricscognitive_complexity,duplicated_lines_density,code_smells,sqale_index,ncloc预期形态在增长中的ncloc上保持平坦或下降。连续两个月上行是安排专项清理的信号——Clean as You Code 只在人们恰好编辑的地方偿还债务。十四、小结这套门禁可复用的关键决策结合 docs/ui-code-quality-gate.md 与仓库实现OpenMetadata 的 UI 质量门禁有几个可直接迁移的工程决策增量作用域优先于绝对清洁所有门禁限定在“变更新增”使 backlog 永不阻塞 PR严重等级是测量结果error/warn 的划分由全仓扫描的实测存量决定且成本数字写在配置注释里晋升路径明确清零 → 重测 → 升 error同一工具链多处调用Agent hook、本地 make 目标、CI 作业共用一份 ESLint 配置保证“本地通过 CI 通过”ESLint 与 Sonar 分工明确ESLint 管能按文件表达的正确性与架构规则SonarCloud Clean-as-You-Code 管只有“新增行”作用域才能成立的条件复杂度、重复、90% 新代码覆盖check 命名与门控解耦ui-sonar-gate这类“shim”check 把条件门控paths-filter、标签与分支保护的安全要求解耦同时用qualitygate.wait取代 API 轮询消除竞态与静默放行。参考文件索引docs/ui-code-quality-gate.md、Makefile、scripts/ui-checkstyle-changed.sh、package.json、eslint.config.mjs、eslint-rules/openmetadata-performance.mjs、eslint-rules/openmetadata-imports.mjs、.github/workflows/ui-checkstyle.yml、.github/workflows/yarn-coverage.yml。【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考