/* eslint-env node */ import process from "node:process"; import js from "@eslint/js"; import nextPlugin from "@next/eslint-plugin-next"; import reactHooks from "eslint-plugin-react-hooks"; import tseslint from "typescript-eslint"; import prettier from "eslint-plugin-prettier"; import globals from "globals"; export default [ // Global ignores { ignores: [ "**/node_modules/**", "**/dist/**", "**/.next/**", "**/build/**", "**/coverage/**", "**/next-env.d.ts", "**/prisma/**", ], }, // Base JS recommendations js.configs.recommended, // Prettier integration { plugins: { prettier }, rules: { "prettier/prettier": "warn" }, }, // TypeScript recommended rules (fast, no type info) ...tseslint.configs.recommended.map((config) => ({ ...config, files: ["**/*.ts", "**/*.tsx"], languageOptions: { ...(config.languageOptions || {}), // Keep config simple: allow both environments; app-specific blocks can tighten later globals: { ...globals.browser, ...globals.node }, }, })), // TypeScript type-aware rules only where we really want them (backend + shared packages) ...tseslint.configs.recommendedTypeChecked.map((config) => ({ ...config, files: ["apps/bff/**/*.ts", "packages/**/*.ts"], languageOptions: { ...(config.languageOptions || {}), parserOptions: { ...((config.languageOptions && config.languageOptions.parserOptions) || {}), projectService: true, tsconfigRootDir: process.cwd(), }, globals: { ...globals.node }, }, })), // Backend & domain packages { files: ["apps/bff/**/*.ts", "packages/domain/**/*.ts"], languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: process.cwd(), }, }, rules: { "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], "no-console": ["warn", { allow: ["warn", "error"] }], }, }, // Strict rules for shared packages { files: ["packages/**/*.ts"], rules: { "@typescript-eslint/no-redundant-type-constituents": "error", "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-unsafe-return": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-member-access": "error", }, }, // Portal (Next.js) rules (flat-config friendly) { ...nextPlugin.configs["core-web-vitals"], files: ["apps/portal/**/*.{js,jsx,ts,tsx}"], }, { // Keep this minimal (defaults-first): only the two classic hook rules. files: ["apps/portal/**/*.{jsx,tsx}"], plugins: { "react-hooks": reactHooks }, rules: { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", }, }, // Portal overrides { files: ["apps/portal/**/*.{ts,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.node }, }, rules: { "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, ], "@next/next/no-html-link-for-pages": "off", }, }, // Prevent hard navigation in authenticated pages { files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"], rules: { "no-restricted-syntax": [ "error", { selector: "MemberExpression[object.name='window'][property.name='location']", message: "Use next/link or useRouter for navigation.", }, ], }, }, // Exceptions for specific files { files: ["apps/portal/src/app/(authenticated)/layout.tsx"], rules: { "no-restricted-imports": "off" }, }, { files: ["apps/portal/src/app/(authenticated)/billing/invoices/[id]/page.tsx"], rules: { "no-restricted-syntax": "off" }, }, // Enforce domain imports architecture { files: ["apps/portal/src/**/*.{ts,tsx}", "apps/bff/src/**/*.ts"], rules: { "no-restricted-imports": [ "error", { patterns: [ { group: ["@customer-portal/domain/**/src/**"], message: "Import from @customer-portal/domain/ instead of internals.", }, ], }, ], }, }, // BFF strict type safety { files: ["apps/bff/**/*.ts"], rules: { "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-return": "error", "@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/require-await": "error", "@typescript-eslint/no-floating-promises": "error", }, }, // Node globals for config files { files: ["*.config.mjs", "apps/portal/next.config.mjs"], languageOptions: { globals: { ...globals.node }, }, }, ];