556 lines
15 KiB
JSON
556 lines
15 KiB
JSON
{
|
|
"env": {
|
|
"es2022": true,
|
|
"node": true
|
|
},
|
|
"ignorePatterns": [
|
|
"**/dist/**",
|
|
"**/*.d.ts"
|
|
],
|
|
"overrides": [
|
|
{
|
|
"files": [
|
|
"**/index.ts",
|
|
"**/index.tsx"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "index.ts files are forbidden. Use explicit file names instead (e.g., UserService.ts, not index.ts).",
|
|
"selector": "Program"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/*/application/ports/*/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Port interface names should not start with 'Get'. Use descriptive names without the 'Get' prefix.",
|
|
"selector": "TSInterfaceDeclaration[id.name=/^Get.*Port$/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Blocker classes/interfaces are not allowed in core. Use Guards in backend.",
|
|
"selector": "TSClassDeclaration[id.name=/Blocker$/], TSInterfaceDeclaration[id.name=/Blocker$/]"
|
|
},
|
|
{
|
|
"message": "Presenter classes/interfaces are not allowed in core. Presenters belong in API or frontend layers.",
|
|
"selector": "TSClassDeclaration[id.name=/Presenter$/], TSInterfaceDeclaration[id.name=/Presenter$/]"
|
|
},
|
|
{
|
|
"message": "DTO classes/interfaces are not allowed in core. DTOs belong in API or frontend layers.",
|
|
"selector": "TSClassDeclaration[id.name=/Dto$/], TSInterfaceDeclaration[id.name=/Dto$/]"
|
|
},
|
|
{
|
|
"message": "ViewModel classes/interfaces are not allowed in core. View Models belong in frontend.",
|
|
"selector": "TSClassDeclaration[id.name=/ViewModel$/], TSInterfaceDeclaration[id.name=/ViewModel$/]"
|
|
},
|
|
{
|
|
"message": "CommandModel classes/interfaces are not allowed in core. Command Models belong in frontend.",
|
|
"selector": "TSClassDeclaration[id.name=/CommandModel$/], TSInterfaceDeclaration[id.name=/CommandModel$/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/application/dto/**/*.ts",
|
|
"core/**/application/dtos/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "core/*/application/dto is forbidden. Use application result models + output ports; DTOs belong in API/website layers.",
|
|
"selector": "Program"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/infrastructure/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "core/*/infrastructure is forbidden. Implementations must live in adapters/ and be wired in apps/.",
|
|
"selector": "Program"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/domain/ports/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "core/*/domain/ports is forbidden. Ports belong in application/ports (or shared application layer), not domain.",
|
|
"selector": "Program"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/shared/presentation/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "core/shared/presentation is forbidden. Presentation belongs in API or website layers.",
|
|
"selector": "Program"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/website/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Guard classes/interfaces are not allowed in frontend. Use Blockers in frontend.",
|
|
"selector": "TSClassDeclaration[id.name=/Guard$/], TSInterfaceDeclaration[id.name=/Guard$/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/api/**/*.ts",
|
|
"apps/website/lib/dtos/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Transport enums must end with 'Enum'.",
|
|
"selector": "TSEnumDeclaration[id.name=/^(?!.*Enum$).+/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/*/application/use-cases/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Use Case classes must end with 'UseCase'.",
|
|
"selector": "TSClassDeclaration[id.name=/^(?!.*UseCase$).+/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/*/application/services/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Application Service classes must end with 'Service'.",
|
|
"selector": "TSClassDeclaration[id.name=/^(?!.*Service$).+/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/website/lib/view-models/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "View Model classes must end with 'ViewModel'.",
|
|
"selector": "TSClassDeclaration[id.name=/^(?!.*ViewModel$).+/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/website/lib/commands/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Command Model classes must end with 'CommandModel'.",
|
|
"selector": "TSClassDeclaration[id.name=/^(?!.*CommandModel$).+/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/website/app/**/page.tsx",
|
|
"apps/website/app/**/page.ts",
|
|
"apps/website/app/**/layout.tsx",
|
|
"apps/website/app/**/layout.ts",
|
|
"playwright.*.config.ts",
|
|
"vitest.*.config.ts",
|
|
"vitest.config.ts"
|
|
],
|
|
"rules": {
|
|
"import/no-default-export": "off",
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Interface names should not start with 'I'. Use descriptive names without the 'I' prefix (e.g., 'LiverCompositor' instead of 'ILiveryCompositor').",
|
|
"selector": "TSInterfaceDeclaration[id.name=/^I[A-Z]/]"
|
|
}
|
|
],
|
|
"@typescript-eslint/no-explicit-any": "off",
|
|
"@typescript-eslint/no-unused-vars": "off"
|
|
}
|
|
},
|
|
{
|
|
"extends": [
|
|
"plugin:import/recommended",
|
|
"plugin:import/typescript"
|
|
],
|
|
"files": [
|
|
"**/*.ts",
|
|
"**/*.tsx"
|
|
],
|
|
"excludedFiles": [
|
|
"playwright.*.config.ts",
|
|
"vitest.*.config.ts",
|
|
"vitest.config.ts"
|
|
],
|
|
"parser": "@typescript-eslint/parser",
|
|
"plugins": [
|
|
"@typescript-eslint",
|
|
"boundaries",
|
|
"import",
|
|
"gridpilot-rules"
|
|
],
|
|
"rules": {
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"@typescript-eslint/no-unused-vars": "error",
|
|
"boundaries/element-types": [
|
|
2,
|
|
{
|
|
"default": "disallow",
|
|
"rules": [
|
|
{
|
|
"allow": [
|
|
"website"
|
|
],
|
|
"from": [
|
|
"website"
|
|
]
|
|
},
|
|
{
|
|
"allow": [
|
|
"api",
|
|
"adapters",
|
|
"core"
|
|
],
|
|
"from": [
|
|
"api"
|
|
]
|
|
},
|
|
{
|
|
"allow": [
|
|
"adapters",
|
|
"core"
|
|
],
|
|
"from": [
|
|
"adapters"
|
|
]
|
|
},
|
|
{
|
|
"allow": [
|
|
"core"
|
|
],
|
|
"from": [
|
|
"core"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"import/no-default-export": "error",
|
|
"import/no-useless-path-segments": "error",
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Default exports are forbidden. Use named exports instead.",
|
|
"selector": "ExportDefaultDeclaration"
|
|
},
|
|
{
|
|
"message": "Interface names should not start with 'I'. Use descriptive names without the 'I' prefix (e.g., 'LiverCompositor' instead of 'ILiveryCompositor').",
|
|
"selector": "TSInterfaceDeclaration[id.name=/^I[A-Z]/]"
|
|
}
|
|
],
|
|
// GridPilot ESLint Rules
|
|
"gridpilot-rules/view-model-taxonomy": "error"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "Default exports are forbidden. Use named exports instead.",
|
|
"selector": "ExportDefaultDeclaration"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/website/**/*.tsx",
|
|
"apps/website/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
"paths": [
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/racing"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/analytics"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/identity"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/media"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/notifications"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/payments"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/shared"
|
|
},
|
|
{
|
|
"message": "Imports from @core are forbidden in website components",
|
|
"name": "@core/social"
|
|
},
|
|
{
|
|
"message": "Imports from @adapters are forbidden in website components",
|
|
"name": "@adapters"
|
|
},
|
|
{
|
|
"message": "Imports from @api are forbidden in website components",
|
|
"name": "@api"
|
|
}
|
|
],
|
|
"patterns": [
|
|
{
|
|
"group": [
|
|
"@core/*"
|
|
],
|
|
"message": "Imports from @core are forbidden in website components"
|
|
},
|
|
{
|
|
"group": [
|
|
"@adapters/*"
|
|
],
|
|
"message": "Imports from @adapters are forbidden in website components"
|
|
},
|
|
{
|
|
"group": [
|
|
"@api/*"
|
|
],
|
|
"message": "Imports from @api are forbidden in website components"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
"message": "ViewModel types must be defined in apps/website/lib/view-models, not in components.",
|
|
"selector": "TSInterfaceDeclaration[id.name=/ViewModel$/], TSTypeAliasDeclaration[id.name=/ViewModel$/], TSClassDeclaration[id.name=/ViewModel$/]"
|
|
},
|
|
{
|
|
"message": "DTO types are forbidden in website components. Use ViewModels instead.",
|
|
"selector": "TSInterfaceDeclaration[id.name=/DTO$/], TSTypeAliasDeclaration[id.name=/DTO$/], TSClassDeclaration[id.name=/DTO$/]"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/api/**/*.test.ts",
|
|
"apps/api/**/*.test.tsx"
|
|
],
|
|
"rules": {
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"no-restricted-syntax": "error"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"apps/website/**/*.test.ts",
|
|
"apps/website/**/*.test.tsx"
|
|
],
|
|
"rules": {
|
|
"@typescript-eslint/no-explicit-any": "off",
|
|
"@typescript-eslint/no-unused-vars": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"tests/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
"paths": [
|
|
{
|
|
"message": "Integration tests must use in-memory adapters, not core directly",
|
|
"name": "@core/*"
|
|
},
|
|
{
|
|
"message": "Integration tests must use in-memory adapters only",
|
|
"name": "@adapters/*"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"tests/e2e/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
"patterns": [
|
|
{
|
|
"group": [
|
|
"**/inmemory/**"
|
|
],
|
|
"message": "E2E tests must use TypeORM/PostgreSQL, not in-memory adapters"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"core/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
"paths": [
|
|
{
|
|
"message": "Use @testing/* from adapters/testing",
|
|
"name": "testing"
|
|
},
|
|
{
|
|
"message": "Core layer should not depend on testing utilities",
|
|
"name": "@testing/*"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"adapters/**/*.ts"
|
|
],
|
|
"rules": {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
"paths": [
|
|
{
|
|
"message": "Use @testing/* from adapters/testing",
|
|
"name": "testing"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"parserOptions": {
|
|
"ecmaVersion": 2022,
|
|
"sourceType": "module"
|
|
},
|
|
"settings": {
|
|
"boundaries/elements": [
|
|
{
|
|
"pattern": "apps/website/**/*",
|
|
"type": "website"
|
|
},
|
|
{
|
|
"pattern": "apps/api/**/*",
|
|
"type": "api"
|
|
},
|
|
{
|
|
"pattern": [
|
|
"adapters/**/*",
|
|
"@adapters/**/*"
|
|
],
|
|
"type": "adapters"
|
|
},
|
|
{
|
|
"pattern": [
|
|
"core/**/*",
|
|
"@core/**/*"
|
|
],
|
|
"type": "core"
|
|
}
|
|
],
|
|
"import/resolver": {
|
|
"typescript": {}
|
|
}
|
|
}
|
|
}
|