build(ci): 建立单仓工具链与质量门禁

统一 pnpm 10 + Node 24.15.0 + Turborepo 单仓,落地 ESLint 9 flat、Prettier、commitlint、husky 与 Gitea CI,作为后续批次的一致门禁。
This commit is contained in:
Stev_Wang committed 2026-11-02 17:20:17 +08:00
1 parent 75cedbe97b
commit b3a2a843bb
21 files changed
+10622

No files matched your search

+226
View File
@@ -0,0 +1,226 @@
// 灵犀 ESLint 9 flat config
// 依据:docs/工程规范/03-格式化与Lint.md
// 分层:根规则(全仓库)→ apps/web(React)→ packages/ui(图标统一出口)→ scripts(允许 console)
import js from '@eslint/js';
import importX from 'eslint-plugin-import-x';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import globals from 'globals';
import tseslint from 'typescript-eslint';
const UI_LIB_MESSAGE = 'UI 组件库只能用 @douyinfe/semi-ui-19(工程规范 03 §3.2);禁装其他组件库。';
const ICON_MESSAGE =
'图标只能从 @lingxi/ui/icons 按语义名导入;禁止直接引用图标库或内联 SVG(AGENTS §3.3)。';
const COLOR_MESSAGE =
'业务代码禁止颜色字面量;颜色只能定义在 packages/ui/src/theme/ 的 token 中(工程规范 03 §3.2)。';
export default tseslint.config(
{
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/coverage/**',
'**/.turbo/**',
'.tools/**',
'design/**',
'docs/**',
'openapi/**',
'prisma/migrations/**',
'项目参考/**',
'playwright-report/**',
'test-results/**',
'**/*.d.ts',
],
},
js.configs.recommended,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
{
name: 'lingxi/base',
languageOptions: {
globals: { ...globals.node, ...globals.browser, ...globals.es2022 },
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
plugins: {
'import-x': importX,
'simple-import-sort': simpleImportSort,
},
settings: {
'import-x/resolver': {
typescript: {
project: ['./tsconfig.json', './apps/*/tsconfig.json', './packages/*/tsconfig.json'],
},
},
},
linterOptions: { reportUnusedDisableDirectives: 'error' },
rules: {
// ---- TypeScript 硬约束(AGENTS §3.2)----
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'separate-type-imports' },
],
'@typescript-eslint/no-unnecessary-condition': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'@typescript-eslint/restrict-template-expressions': [
'error',
{ allowNumber: true, allowBoolean: true },
],
'@typescript-eslint/naming-convention': [
'error',
{ selector: 'typeLike', format: ['PascalCase'] },
{ selector: 'enumMember', format: ['UPPER_CASE'] },
],
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
// ---- 导入与依赖方向 ----
'import-x/no-cycle': ['error', { maxDepth: 8, ignoreExternal: false }],
'import-x/no-self-import': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
// ---- 通用 ----
'no-console': 'error',
eqeqeq: ['error', 'always'],
'no-var': 'error',
'prefer-const': 'error',
complexity: ['warn', { max: 15 }],
'max-lines-per-function': [
'warn',
{ max: 50, skipBlankLines: true, skipComments: true, IIFEs: true },
],
},
},
// 纯 JS 配置文件不做类型检查
{
name: 'lingxi/js-config',
files: ['**/*.{js,mjs,cjs}'],
extends: [tseslint.configs.disableTypeChecked],
rules: {
'no-console': 'off',
'import-x/no-cycle': 'off',
},
},
// 应用层:Web(React 19)
{
name: 'lingxi/apps-web',
files: ['apps/web/**/*.{ts,tsx}'],
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'jsx-a11y': jsxA11y,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'jsx-a11y/alt-text': 'error',
'jsx-a11y/no-autofocus': 'warn',
'@typescript-eslint/no-restricted-imports': [
'error',
{
patterns: [
{ group: ['@douyinfe/semi-ui', '@douyinfe/semi-ui/*'], message: UI_LIB_MESSAGE },
{
group: [
'@remixicon/react',
'react-icons',
'react-icons/*',
'lucide-react',
'lucide',
'semi-icons',
'@douyinfe/semi-icons',
'@heroicons/*',
'@ant-design/icons',
],
message: ICON_MESSAGE,
},
{
group: [
'antd',
'@arco-design/web-react',
'element-plus',
'naive-ui',
'@mui/*',
'@chakra-ui/*',
],
message: UI_LIB_MESSAGE,
},
],
},
],
'no-restricted-syntax': [
'error',
{
selector: 'Literal[value=/^#(?:[0-9a-fA-F]{3,8})$/]',
message: COLOR_MESSAGE,
},
{
selector: 'Literal[value=/^rgba?\\(/]',
message: COLOR_MESSAGE,
},
{
selector: "JSXOpeningElement[name.name='svg']",
message: ICON_MESSAGE,
},
],
},
},
// packages/ui:图标统一出口(只有 icons.ts 可直接引用 Remix Icon)
{
name: 'lingxi/ui-icons',
files: ['packages/ui/src/**/*.{ts,tsx}'],
rules: {
'@typescript-eslint/no-restricted-imports': [
'error',
{ patterns: [{ group: ['@remixicon/react'], message: ICON_MESSAGE }] },
],
},
},
{
name: 'lingxi/ui-icons-exit',
files: ['packages/ui/src/icons.ts'],
rules: {
'@typescript-eslint/no-restricted-imports': ['error', { patterns: [] }],
},
},
// Nest 模块装饰器类按框架约定是空类
{
name: 'lingxi/nest-modules',
files: ['apps/api/**/*.module.ts'],
rules: {
'@typescript-eslint/no-extraneous-class': 'off',
},
},
// 脚本层:允许 console(CLI 输出)
{
name: 'lingxi/scripts',
files: ['scripts/**/*.{ts,mjs,js}', '**/scripts/**/*.ts', 'prisma/seed.ts', 'e2e/**/*.ts'],
rules: {
'no-console': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
},
},
// 测试文件:允许更松的表达
{
name: 'lingxi/tests',
files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.int.spec.ts', 'e2e/**/*.ts'],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'max-lines-per-function': 'off',
complexity: 'off',
},
},
);