// Repo-wide ESLint flat config. Packages inherit these rules; add // package-specific overrides here (scoped by `files`) rather than with // per-package config files, so rules stay consistent across the monorepo. import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import prettier from 'eslint-config-prettier'; export default tseslint.config( { ignores: [ '**/dist/**', // Vendored third-party apps bundled into plugins (drawio) — never linted. 'packages/plugins/*/vendor/**', '**/node_modules/**', '**/coverage/**', '**/.pnpm-store/**', '**/*.gen.ts', // Runtime data volumes (installed plugin bundles, uploads) — not source. 'apps/api/data/**', ], }, js.configs.recommended, ...tseslint.configs.recommended, prettier, { // Plain-Node maintenance/build scripts (no TypeScript, no bundler). files: [ 'scripts/**/*.mjs', 'deploy/**/*.mjs', 'packages/plugins/*/build.mjs', 'apps/api/scripts/**/*.mjs', ], languageOptions: { globals: { console: 'readonly', process: 'readonly', URL: 'readonly', fetch: 'readonly', Buffer: 'readonly', setTimeout: 'readonly', }, }, }, { // Classic browser scripts served verbatim from public/ (theme-init.js, // issue #180): no bundler, no modules, browser globals only. files: ['apps/web/public/*.js'], languageOptions: { globals: { window: 'readonly', document: 'readonly', }, }, }, { // Icon-only controls go through the shared components (issue #300). // Gluing `icon-button` onto a raw element copies the looks but skips the // contract that guarantees an accessible name — that is how the // notification bell drifted into its own size and focus ring. files: ['apps/web/src/**/*.tsx'], ignores: ['apps/web/src/components/IconButton.tsx'], rules: { 'no-restricted-syntax': [ 'error', { selector: 'JSXOpeningElement[name.name=/^(button|a|Link)$/] > JSXAttribute[name.name="className"] > Literal[value=/(^|\\s)icon-button(\\s|$)/]', message: 'Use (or for navigation) from components/IconButton instead of putting the icon-button class on a raw element — the component enforces the accessible name.', }, { selector: 'JSXOpeningElement[name.name=/^(button|a|Link)$/] > JSXAttribute[name.name="className"] > JSXExpressionContainer > TemplateLiteral > TemplateElement[value.raw=/(^|\\s)icon-button(\\s|$)/]', message: 'Use (or for navigation) from components/IconButton instead of putting the icon-button class on a raw element — the component enforces the accessible name.', }, ], }, }, { rules: { // Unused values are usually bugs; underscore-prefix marks intentional ones. '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], // `any` defeats the shared Zod/type contracts between client and server. '@typescript-eslint/no-explicit-any': 'error', }, }, );