eslint.config.mjs
Rama main · réplica de lectura del repositorio original.
import { FlatCompat } from "@eslint/eslintrc";
import { readFileSync } from "node:fs";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({ baseDirectory: __dirname });
/**
* Ce fichier est SYNCHRONISÉ vers le build self-host (mono-tenant), où le
* client `prisma` est un PrismaClient ordinaire et où `$transaction` est
* parfaitement légitime — 9 sites l'utilisent. Y appliquer la règle F5.1
* ci-dessous rendrait `npm run lint` rouge chez tous les auto-hébergés.
*
* Plutôt qu'un jumeau d'overlay de plus à maintenir (et à voir diverger), on
* détecte la présence du client ÉTENDU, seule raison d'être de la règle : le
* marqueur `TenantAwarePrisma` n'existe que dans le `lib/prisma.ts` du SaaS.
* Si un jour l'extension disparaît, la règle s'éteint d'elle-même — ce qui est
* exactement le comportement voulu.
*/
const hasExtendedPrismaClient = (() => {
try {
return readFileSync(`${__dirname}/lib/prisma.ts`, "utf8").includes(
"TenantAwarePrisma",
);
} catch {
return false;
}
})();
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
ignores: [
".next/**",
"node_modules/**",
"prisma/migrations/**",
"next-env.d.ts",
],
},
...(hasExtendedPrismaClient
? [
{
// F5.1 — `prisma.$transaction` sur le client étendu (lib/prisma.ts) n'ouvre
// aucune transaction utile : l'extension redispatche chaque opération vers
// un autre PrismaClient, qui les exécute en autocommit hors transaction.
// Le type l'interdit déjà (le `$transaction` est retiré de
// `TenantAwarePrisma`) ; cette règle n'existe que pour que le message soit
// lisible plutôt qu'un « Property '$transaction' does not exist ».
//
// Portée : `app/**` et `lib/**`, les seuls endroits où le nom `prisma`
// désigne le client étendu. Ailleurs il désigne un vrai client et
// `$transaction` y est légitime : les `scripts/*.mjs` autonomes font leur
// propre `new PrismaClient()`, et `scripts/public-overlay/**` est le jumeau
// self-host mono-tenant. `basePrisma`, `adminPrisma` et
// `getTenantPrisma(slug)` ne sont jamais visés — le sélecteur porte sur le
// nom `prisma` seul. Le garde-fou qui couvre TOUT reste le type.
files: ["app/**/*.{ts,tsx}", "lib/**/*.{ts,tsx}"],
rules: {
"no-restricted-syntax": [
"error",
{
selector:
'MemberExpression[object.name="prisma"][property.name="$transaction"]',
message:
"prisma.$transaction n'a AUCUNE atomicité (client étendu multi-tenant, cf. rapport-security.md §F5.1). Utiliser withTenantSchema(slug, (tx) => …) de lib/tenant.ts.",
},
],
},
},
]
: []),
];
export default eslintConfig;