/** * 架构红线的**单源装配**:composition-root 白名单、规则名单、规则集拼装、语言层全局。 * * 根 `eslint.config.js` 与 `packages/config/eslint/rules.js` 都从这里 import,不各自再抄一份 * ——历史上两处漂移过(白名单、globals、规则集不一致)。改红线装配只改这一个文件。 * * 白名单是绝对路径,规则 C 按 `绝对路径 === r || 绝对路径.endsWith(r)` 匹配。新增豁免点 * 先改任务文档(arch-root-solution.md 决定 1)再改这里。 */ import path from 'node:path' import { fileURLToPath } from 'node:url' const here = path.dirname(fileURLToPath(import.meta.url)) // tools/eslint-rules/ 上两级即仓库根。 const repoRoot = path.resolve(here, '..', '..') /** * 规则 C 豁免点(composition root):这些文件允许顶层建连接。 * 绝对路径,精确匹配,不放宽到整个 app 目录。 */ export const COMPOSITION_ROOTS = [ // voko-server 的 runtime 工厂负责读取 env、创建连接并统一管理资源生命周期。 path.join(repoRoot, 'apps/voko-server/src/assembly/runtime.js'), // worker 进程入口与数据库 init / migrate / status / baseline 命令各自是独立 composition root。 path.join(repoRoot, 'apps/voko-server/src/voko-worker-main.js'), path.join(repoRoot, 'apps/voko-server/src/db/init-current-schema.js'), path.join(repoRoot, 'apps/voko-server/src/db/baseline-existing-database.js'), path.join(repoRoot, 'apps/voko-server/src/db/migrate-database.js'), path.join(repoRoot, 'apps/voko-server/src/db/migration-status.js'), ] /** * Vite 配置文件白名单:构建期读 `process.env`(NODE_ENV / 代理目标等)是 Vite 配置的合理需求, * 不是散读 env。用路径后缀匹配覆盖 `packages/config/vite/base.js` 与各 app 的 `vite.config.js`。 * 它们不会建 DB 连接,整文件豁免连接检查也无副作用。 */ export const VITE_CONFIG_WHITELIST = [ 'packages/config/vite/base.js', 'vite.config.js', ] /** * tools/scripts/ 下的脚本都是独立可执行入口(自带 shebang、直接 `node tools/scripts/x.js` 跑), * 每个进程各自是自己的 composition root:读 env、顶层建连接都是合理的。整目录一条豁免, * 不逐个文件散白。以 `/` 结尾表示目录前缀匹配(见 no-conn-outside-root.js 的匹配逻辑)。 */ export const TOOLS_SCRIPTS_ROOTS = ['tools/scripts/', '/tools/'] /** 规则 C 的完整连接创建豁免集。 */ export const CONN_WHITELIST = [ ...COMPOSITION_ROOTS, ...VITE_CONFIG_WHITELIST, ...TOOLS_SCRIPTS_ROOTS, ] /** 架构红线规则名(供测试/下游静态断言「红线确实进了装配」)。 */ export const ENFORCED_RULE_NAMES = Object.freeze([ 'drama/no-deep-import', 'drama/no-subdomain-deep-import', 'drama/no-conn-outside-root', 'drama/no-app-import', 'drama/no-drama-rule-disable', ]) /** * 四条架构红线 + 禁 disable 绕行 + 通用 JS 规则,一份定义供全部消费方复用。 * @param {string[]} [connWhitelist] 允许顶层建连接的 composition root 文件集。 * @returns {import('eslint').Linter.RulesRecord} */ export function buildArchRules(connWhitelist = CONN_WHITELIST) { return { 'drama/no-deep-import': 'error', 'drama/no-subdomain-deep-import': 'error', 'drama/no-conn-outside-root': ['error', { connExemptFiles: connWhitelist }], 'drama/no-app-import': 'error', 'drama/no-drama-rule-disable': 'error', 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 'no-undef': 'error', } } /** Node 24 运行时全局(ESM/CJS 通用),单源,避免根配置与 config 预设各写一份漂移。 */ export const BASE_GLOBALS = { process: 'readonly', console: 'readonly', URL: 'readonly', URLSearchParams: 'readonly', Buffer: 'readonly', fetch: 'readonly', setTimeout: 'readonly', clearTimeout: 'readonly', setInterval: 'readonly', clearInterval: 'readonly', structuredClone: 'readonly', // Web 平台 API(Node 24 与浏览器均为全局;api-sdk 走 fetch/流式解析需要)。 Response: 'readonly', Headers: 'readonly', ReadableStream: 'readonly', TextDecoder: 'readonly', TextEncoder: 'readonly', AbortController: 'readonly', AbortSignal: 'readonly', DOMException: 'readonly', } /** 浏览器运行时全局(Vue 组件与前端 app 的 .js 用),单源。 */ export const BROWSER_GLOBALS = { window: 'readonly', document: 'readonly', navigator: 'readonly', localStorage: 'readonly', sessionStorage: 'readonly', HTMLElement: 'readonly', Image: 'readonly', Audio: 'readonly', MouseEvent: 'readonly', KeyboardEvent: 'readonly', CustomEvent: 'readonly', performance: 'readonly', requestAnimationFrame: 'readonly', cancelAnimationFrame: 'readonly', } /** CommonJS 脚本全局(`.cjs`:迁移、table 定义等)。 */ export const COMMONJS_GLOBALS = { ...BASE_GLOBALS, require: 'readonly', module: 'readonly', exports: 'readonly', __dirname: 'readonly', __filename: 'readonly', }