- 建立 pnpm workspace + Turborepo 单仓、ESLint 9/Prettier/commitlint/husky 门禁 - NestJS + Fastify API:统一错误体、trace_id、Zod 管道、/health 真实依赖探活、OpenAPI 3.1 契约 - Prisma 基线:outbox_events/idempotency_keys/system_configs 与首版迁移、幂等种子 - shared 契约包:错误码、权限点、ULID、游标分页、命名/时间工具 - ui 包与 Web 壳层:Semi + Remix 图标统一出口、Cloud Blue 视觉、登录/工作台/错误页 - worker 骨架、Playwright 冒烟、Gitea CI、OpenAPI 快照与 oasdiff 回退脚本 - 补充 A0 实施报告与本地开发说明
This commit is contained in:
1 parent
9c3ab9768a
commit
04f49690f4
155 files changed
+18402
-28
No files matched your search
@@ -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',
|
||||
},
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user