/** * eslint.config.mjs - flat config (ESLint 9+). * * The interesting rules in this file are the *import boundaries*: they enforce the * three-layer architecture from REBUILD.md § 4. Examples of what they prevent: * * • A `components/**` file importing `lib/db/**` (UI must not query the DB directly). * • A `lib/db/**` file importing from `lib/rbac/**` (the DB layer must not know about RBAC). * • A `lib/pdns/**` file importing from `lib/rbac/**` or `lib/audit/**` (PDNS client is * a pure protocol adapter, RBAC and audit happen above it). * * When you find yourself wanting to disable one of these rules, that's a signal you're * about to break the architecture. Stop and either refactor or write an ADR explaining * why this case is the exception. */ import js from "@eslint/js"; import tseslint from "typescript-eslint"; // `eslint-plugin-import-x` is the actively-maintained, ESLint-10-compatible // fork of `eslint-plugin-import` (the original crashes on ESLint 10's rule // API). Registered under the `import` plugin key below so the existing // `import/...` rule names and inline disable directives keep working. import importPlugin from "eslint-plugin-import-x"; // Next's own `eslint-config-next` bundles `eslint-plugin-react`, which calls // APIs ESLint 10 removed (`context.getFilename`) and crashes the entire lint // run. Rather than run that brittle config, we compose the two highest-value, // ESLint-10-compatible pieces it vendors - the React Hooks rules and the // @next/next framework checks - directly. `eslint-config-next` stays a // devDependency precisely because it's what installs these into node_modules. import reactHooks from "eslint-plugin-react-hooks"; import nextPlugin from "@next/eslint-plugin-next"; export default tseslint.config( // === Files to ignore globally === { ignores: [ ".next/**", "node_modules/**", "dist/**", "build/**", "coverage/**", "drizzle/meta/**", // Legacy reference - never lint, never modify. "PowerDNS-Admin/**", // postcss.config.mjs / eslint.config.mjs aren't in tsconfig.json's // `include` (which is .ts/.tsx only). The type-aware lint rules // need the file to be part of the TS project service to resolve // types; without that we get a "not found by the project // service" parser error. Both files are declarative config with // no runtime logic worth linting - skip them. "postcss.config.mjs", "eslint.config.mjs", "docker/entrypoint.mjs", // Plain Node ESM scripts in scripts/ that aren't part of the TS // project (Playwright + docker-stack drivers). The .ts ones in the // same folder stay linted. "scripts/*.mjs", ], }, // === Base recommended configs === js.configs.recommended, ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked, // === TypeScript project setup === { languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, }, // === Project-wide rules === { plugins: { import: importPlugin }, // `import/no-default-export` operates on the AST - no resolver needed. The // three-layer import boundary (ADR-0004) is enforced separately by // `no-restricted-imports` patterns in the per-layer `files` blocks below. // (It used to be `import/no-restricted-paths`, which silently never fired: // that rule needs a resolver to map the `@/` alias to a path, and none was // configured - see ADR-0013.) rules: { // CONTRIBUTING.md § Language: no default exports. Named exports refactor cleanly. "import/no-default-export": "error", // CONTRIBUTING.md § Errors: no silent catches. "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, ], "no-empty": ["error", { allowEmptyCatch: false }], // Type safety. `any` is reserved for marked boundaries; everywhere else it's an error. "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unsafe-return": "error", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/await-thenable": "error", // Relax `checksVoidReturn.attributes` because React internally // void-wraps event-handler return values - `