Ctrl K
ComunidadGuías…

eslint.config.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'
import nextTypescript from 'eslint-config-next/typescript'
import prettier from 'eslint-config-prettier'

/**
 * Imported directly, not through `FlatCompat`.
 *
 * eslint-config-next ships flat config from v16 on, and the compatibility
 * bridge chokes on it -- it tries to validate a flat array against the old
 * schema and dies inside its own error formatter, which is a stack trace rather
 * than a message. One import each is also simply what the config now is.
 */

const GERMAN_IN_COMPONENT =
  'German text belongs in src/messages, not in a component. ' +
  'Use useTranslations() / getTranslations(). See docs/ui-conventions.md'

const config = [
  {
    ignores: [
      // A git worktree is a checkout of its own, at its own commit, with its own
      // lint run -- and this project puts them INSIDE the repository, under
      // .claude/worktrees/. Linting one from here reports findings about code
      // that is not on this branch and cannot be fixed from this directory.
      //
      // The symptom was the build output rather than the source: once a worktree
      // had been built, `pnpm lint` in the main checkout drowned in hundreds of
      // no-require-imports errors from minified vendor chunks -- exactly what
      // the patterns below exist to prevent, walked around by one directory
      // level. CI never saw it, because CI has no worktrees.
      '.claude/worktrees/**',
      // Prefixed with **/ rather than anchored at the root: an artefact
      // directory one level down is the same artefact directory.
      '**/.next/**',
      '**/node_modules/**',
      '**/coverage/**',
      'next-env.d.ts',
      // Test and build artefacts: minified vendor bundles that would otherwise
      // drown the report in thousands of irrelevant findings.
      '**/playwright-report/**',
      '**/blob-report/**',
      '**/test-results/**',
      // The bundled collaboration server: generated output, not source.
      '**/dist/**',
      // The help site is a package of its own (Astro, its own lockfile) whose
      // code is two config scripts; the Next rules here do not apply to it.
      'help/**',
    ],
  },

  ...nextCoreWebVitals,
  ...nextTypescript,
  prettier,

  {
    rules: {
      '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
      ],
    },
  },

  // --- goodworkshop-ui + i18n: what may not appear in UI code ----------------
  //
  // Both guardrails live in ONE `no-restricted-syntax` entry on purpose. Flat
  // config REPLACES a rule's options when a later block matches the same file,
  // so splitting them into two blocks over the same glob silently switched the
  // first one off -- which is exactly the kind of guardrail failure that looks
  // like everything is fine.
  //
  // No raw hex colours: category colours go through the .cat-* OKLCH tokens and
  // accents through --brand-*, or dark mode, the print stylesheet and tenant
  // branding end up disagreeing about a colour.
  //
  // No German prose: umlauts and ß are the cheap, reliable tell. It will not
  // catch "Save me" -- nothing lint-shaped would -- but it does catch the
  // realistic case, which is somebody adding a German label next to fifteen
  // translated ones because that is what the file used to look like.
  {
    files: ['src/components/**/*.{ts,tsx}', 'src/features/**/*.{ts,tsx}', 'src/app/**/*.{ts,tsx}'],
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector: 'Literal[value=/#[0-9a-fA-F]{3}([0-9a-fA-F]{3}([0-9a-fA-F]{2})?)?\\b/]',
          message:
            'No raw hex colours in UI code. Category colours go through the .cat-* tokens, accents through --brand-*. See docs/ui-conventions.md',
        },
        {
          selector: 'Literal[value=/[äöüÄÖÜß]/]',
          message: GERMAN_IN_COMPONENT,
        },
        {
          selector: 'TemplateElement[value.raw=/[äöüÄÖÜß]/]',
          message: GERMAN_IN_COMPONENT,
        },
        {
          selector: 'JSXText[value=/[äöüÄÖÜß]/]',
          message: GERMAN_IN_COMPONENT,
        },
      ],
    },
  },

  // German is the SOURCE text, so these three keep the colour rule and lose the
  // language one: the reference agenda and the assertions that read it are
  // written in German, and translating a fixture would mean the export
  // snapshots stop showing what a German facilitator actually sees. The export
  // route's umlauts ARE the characters being transliterated, not prose.
  //
  // The hex selector is restated rather than inherited -- see the note above
  // about replacement.
  {
    // Scoped to the SAME directories as the block above. Listing bare
    // `src/**/*.test.ts` here would hand the colour rule to files it never
    // applied to -- src/lib/color's own tests are full of hex values on
    // purpose, because hex is what they convert.
    files: [
      'src/components/**/*.test.{ts,tsx}',
      'src/features/**/*.test.{ts,tsx}',
      'src/app/**/*.test.{ts,tsx}',
      'src/components/**/fixtures/**/*.{ts,tsx}',
      'src/features/**/fixtures/**/*.{ts,tsx}',
      'src/app/api/w/**/export/route.ts',
    ],
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector: 'Literal[value=/#[0-9a-fA-F]{3}([0-9a-fA-F]{3}([0-9a-fA-F]{2})?)?\\b/]',
          message:
            'No raw hex colours in UI code. Category colours go through the .cat-* tokens, accents through --brand-*. See docs/ui-conventions.md',
        },
      ],
    },
  },

  // --- RLS seam: the raw db handle must never escape src/server/db -----------
  // Every query has to run inside withTenant() so that app.tenant_id is set.
  // An import of the raw client anywhere else is a tenant-isolation bug.
  {
    files: ['src/**/*.{ts,tsx}'],
    ignores: ['src/server/db/**'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          patterns: [
            {
              group: ['**/server/db/client', '@/server/db/client'],
              message:
                'Das rohe db-Handle ist modul-privat. Nutze withTenant() / withAuth() / withOps() aus @/server/db. Siehe Plan §RLS.',
            },
          ],
        },
      ],
    },
  },
]

export default config