config.js 4.98 KB
/**
 * 架构红线的**单源装配**: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',
}