import eslintReact from '@eslint-react/eslint-plugin' import astroEslintParser from 'astro-eslint-parser' import { defineConfig } from 'eslint/config' import eslintConfigPrettier from 'eslint-config-prettier' import eslintPluginAstro from 'eslint-plugin-astro' import muiPathImports from 'eslint-plugin-mui-path-imports' import noRelativeImportPaths from 'eslint-plugin-no-relative-import-paths' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import simpleImportSort from 'eslint-plugin-simple-import-sort' import unusedImports from 'eslint-plugin-unused-imports' import path from 'path' import tseslint from 'typescript-eslint' import noDirectContextAccess from './eslint-rules/no-direct-context-access.mjs' import noOpaqueDestructiveFilter from './eslint-rules/no-opaque-destructive-filter.mjs' import noTransactionEscape from './eslint-rules/no-transaction-escape.mjs' import noUnknownApiPath from './eslint-rules/no-unknown-api-path.mjs' import noUntypedLocalPaths from './eslint-rules/no-untyped-local-paths.mjs' const neverkin = { rules: { 'no-direct-context-access': noDirectContextAccess, 'no-opaque-destructive-filter': noOpaqueDestructiveFilter, 'no-transaction-escape': noTransactionEscape, 'no-unknown-api-path': noUnknownApiPath, 'no-untyped-local-paths': noUntypedLocalPaths, }, } export default defineConfig( { ignores: [ 'app/calliope-websockets/dist', 'app/rhea-backend/dist', 'app/rhea-backend/prisma', 'app/styx-frontend/build', 'app/orpheus-mcp/dist', 'app/ts-shared/dist', 'library/tiptap-schema/dist', 'app/thetis-landing/dist', 'app/thetis-landing/.astro', 'test/e2e/dist', '*.gen.ts', 'node_modules', '.husky', 'app/thetis-landing/src/components/common/BasicScripts.astro', 'app/thetis-landing/src/components/common/ApplyColorMode.astro', 'library/openapi-fetch/src/rhea-api.ts', 'library/**/dist', ], }, { extends: [...tseslint.configs.recommended], files: ['**/*.json'], rules: { '@typescript-eslint/no-unused-expressions': 'off', }, }, { extends: [tseslint.configs.recommended], files: ['**/*.{mjs,js,ts,tsx}'], plugins: { 'simple-import-sort': simpleImportSort, 'unused-imports': unusedImports, 'no-relative-import-paths': noRelativeImportPaths, 'mui-path-imports': muiPathImports, }, rules: { 'linebreak-style': ['error', 'unix'], 'no-tabs': 'off', 'no-trailing-spaces': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-explicit-any': 'error', 'object-curly-spacing': 'off', 'no-case-declarations': 'off', 'space-before-function-paren': 'off', 'simple-import-sort/imports': 'error', 'simple-import-sort/exports': 'error', 'unused-imports/no-unused-imports': 'error', 'mui-path-imports/mui-path-imports': 'error', '@typescript-eslint/no-unused-vars': 'off', 'unused-imports/no-unused-vars': [ 'warn', { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_', }, ], }, }, { // Local paths go through the typed router files: ['app/styx-frontend/src/**/*.{ts,tsx}'], ignores: ['app/styx-frontend/src/api/**'], plugins: { neverkin }, rules: { 'neverkin/no-untyped-local-paths': 'error', }, }, { files: ['app/styx-frontend/src/**/*.{ts,tsx}'], rules: { 'no-relative-import-paths/no-relative-import-paths': [ 'error', { allowSameFolder: true, allowedDepth: 2, rootDir: path.relative(process.cwd(), path.join(import.meta.dirname, 'app/styx-frontend/src')), prefix: '@', }, ], }, }, { // Custom rules for rhea-backend to enforce moonflower patterns files: ['app/rhea-backend/src/routers/**/*.ts'], plugins: { neverkin }, rules: { 'neverkin/no-direct-context-access': 'warn', }, }, { // A destructive filter that widens to every row is silent data loss. // Needs type information: the filter's columns are read off the checked type. files: ['app/rhea-backend/src/**/*.ts'], plugins: { neverkin }, languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, rules: { 'neverkin/no-opaque-destructive-filter': 'error', 'neverkin/no-transaction-escape': 'error', 'no-restricted-properties': [ 'error', ...['$queryRaw', '$queryRawUnsafe', '$queryRawTyped', '$executeRaw', '$executeRawUnsafe'].map( (property) => ({ property, message: 'Raw SQL is forbidden. Use the typed Prisma query API.', }), ), ], }, }, { files: ['app/rhea-backend/src/**/*.spec.ts'], plugins: { neverkin }, rules: { 'neverkin/no-unknown-api-path': 'error', }, }, { files: ['**/*.{ts,tsx}'], rules: { 'no-restricted-imports': [ 'error', { paths: [ { name: 'zod', importNames: ['default'], message: "Use `import { z } from 'zod'`. The default import emits `z.z.core.*` into the .d.ts, which ts-morph (Moonflower) resolves to `any`.", }, ], }, ], }, }, { ignores: ['app/rhea-backend/**'], extends: [eslintReact.configs['recommended-typescript']], rules: { // 'react-hooks/set-state-in-effect': 'off', // 'react-hooks/refs': 'off', // 'react-hooks/immutability': 'off', // 'react-hooks/preserve-manual-memoization': 'off', // 'react/prop-types': 'off', '@eslint-react/no-context-provider': 'off', '@eslint-react/no-array-index-key': 'off', '@eslint-react/purity': 'off', '@eslint-react/use-state': 'off', '@eslint-react/no-unnecessary-use-prefix': 'off', '@eslint-react/set-state-in-effect': 'off', '@eslint-react/naming-convention-ref-name': 'off', '@eslint-react/no-create-ref': 'off', }, files: ['**/*.{ts,tsx}'], settings: { react: { version: '19.x', }, }, }, ...eslintPluginAstro.configs['flat/recommended'], { files: ['app/thetis-landing/**/*.astro'], ignores: [ 'app/thetis-landing/src/components/common/BasicScripts.astro', 'app/thetis-landing/src/components/common/Image.astro', ], languageOptions: { parser: astroEslintParser, parserOptions: { parser: '@typescript-eslint/parser', extraFileExtensions: ['.astro'], }, }, }, eslintConfigPrettier, eslintPluginPrettierRecommended, { files: ['**/*.astro/*.js', '**/*.astro/*.ts'], rules: { 'prettier/prettier': 'off', }, }, )