pnpm check, une chaîne de gardes
Une seule commande avant chaque commit : des gardes rapides d’abord, puis les types de trois projets TypeScript et le lint de tout le code écrit à la main. Ce que chaque étape couvre, ce qu’elle ne voit pas, et ce que l’extension du lint aux paquets a trouvé dès son premier passage.
Précédemment. Le site est utilisable au clavier et avec un lecteur d’écran. Toutes les fonctions sont en place ; reste à garantir qu’elles continuent de marcher après chaque modification.
Une erreur qu’un outil peut trouver ne devrait jamais attendre qu’un humain la croise. Cette partie présente les vérifications automatiques du projet. Ce chapitre les prend ensemble, comme une chaîne : ce que lance pnpm check, dans quel ordre, et ce que chaque maillon couvre. Le chapitre 2 entre dans le typecheck et le lint, le chapitre 3 dans les gardes écrites pour ce projet.
Une commande, lancée avant chaque commit ✅#
pnpm check est un script de la racine. Il enchaîne les vérifications avec && : la première qui échoue arrête tout, et son message est le dernier affiché.
"@dnd-kit/utilities": "^3.2.2", "@tiptap/core": "^3.27.1", "@tiptap/html": "^3.27.1", "@workspace/finder-core": "workspace:*", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "gray-matter": "^4.0.3",L’ordre n’est pas indifférent : les gardes maison passent d’abord, parce qu’elles prennent une seconde et qu’elles expliquent l’erreur en une phrase. Le typecheck et le lint, qui relisent des centaines de fichiers, viennent ensuite. Une erreur bon marché à trouver est trouvée tôt.
Étape | Question posée | Si elle échoue |
|---|---|---|
check-next-conventions | Les fichiers de convention de Next (proxy, instrumentation) sont-ils là où Next les lit ? | Un fichier ignoré sans un mot de Next : le filtre d’authentification ne s’exécuterait pas. |
check-tour-targets | Chaque étape d’un tutoriel interactif vise-t-elle un élément qui existe, et chaque tuto est-il enregistré et bien désigné par les fiches ? | Un tutoriel bloqué chez l’utilisateur, ou introuvable. |
build-procedure-map | La carte des procédures de la documentation correspond-elle au backend ? | Des parcours de la doc qui décrivent un code disparu. |
check-doc-entities | Les entités, procédures et sections citées par la doc existent-elles ? | Une carte des entités fausse, sans erreur visible. |
pnpm clean | — | Efface .next : les types générés périmés n’entrent pas dans le typecheck. |
pnpm typecheck | Les types sont-ils cohérents, dans les trois projets TypeScript ? | Une procédure et le composant qui l’appelle ne parlent plus de la même chose. |
pnpm lint | Le code respecte-t-il les règles de React et du langage, sans un seul avertissement ? | Un motif fautif, du code mort, un composant que le React Compiler renonce à optimiser. |
Showing entries1to7entries
pnpm check passe avant chaque commit, pas seulement avant une mise en production. Un commit qui ne passe pas les vérifications en cache d’autres : la prochaine erreur se trouvera au milieu de celles qu’on a laissées.
Pourquoi effacer .next avant de vérifier les types#
pnpm clean paraît hors sujet au milieu des vérifications. Il est là parce que le tsconfig du web inclut tous les fichiers .ts de son dossier :
**/*.ts couvre aussi le dossier .next, où Next écrit des fichiers de types qu’il génère (les routes, les paramètres des pages). Ces fichiers datent du dernier pnpm dev ou du dernier build : une page renommée depuis y figure encore, et le typecheck échoue sur un fichier que personne n’a écrit. Le projet a connu trois de ces faux échecs avant que pnpm check ne commence par vider .next.
pnpm clean vide le .next que pnpm dev utilise. Après un pnpm check lancé pendant que pnpm dev tourne, une modification peut ne plus apparaître dans le navigateur : arrêter pnpm dev, le relancer, recharger la page.
Trois projets TypeScript#
Le dépôt n’est pas un projet TypeScript mais plusieurs, chacun avec son tsconfig.json (partie « Configuration »). pnpm typecheck les vérifie l’un après l’autre :
Projet | Commande | Ce qu’il vérifie |
|---|---|---|
apps/web | pnpm --filter web typecheck | L’application, et avec ses propres règles tout ce qu’elle inclut : les contrats, le moteur du Finder, la configuration partagée. |
packages/backend | pnpm --filter backend typecheck | Le backend et les contrats, avec les règles du backend. |
La racine | tsc -p tsconfig.json | prisma.config.ts, les scripts TypeScript de prisma/, et les tests de bout en bout (playwright.config.ts, e2e/, chapitre 4). |
Showing entries1to3entries
Les contrats sont donc vérifiés deux fois, une fois par chacun de leurs lecteurs : c’est voulu, chaque projet les compile avec ses propres alias et ses propres options, et une incompatibilité avec l’un des deux doit se voir.
La troisième ligne est récente. La configuration de la racine ne servait qu’à l’éditeur : prisma.config.ts n’était vérifié par rien. Un fichier que personne ne vérifie finit par contenir une erreur que personne ne voit.
// ⚠ Fichier introuvable : tsconfig.json// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Le lint, de tout le code écrit à la main#
pnpm lint relisait seulement apps/web. Le backend, les contrats et le moteur du Finder — la moitié du code, celle qui touche la base et décide des droits — n’étaient relus par aucune règle. Le script de la racine lance maintenant deux passes, la seconde sur les paquets et sur les tests de bout en bout (chapitre 4) :
"@tiptap/core": "^3.27.1",La seconde passe lit la configuration de la racine, eslint.config.mjs, qui reprend celle du web : une seule liste de règles pour tout le dépôt. C’est aussi celle que l’éditeur applique à un fichier du backend ou des tests, puisqu’il prend la configuration la plus proche du fichier. Elle n’en diffère que d’une règle :
// ⚠ Fichier introuvable : eslint.config.mjs// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.La règle @next/next/no-html-link-for-pages cherche un dossier pages/ de Next à la racine du projet, et affiche un avertissement quand elle n’en trouve pas. Le backend n’écrit aucun lien HTML : la règle est sans objet hors de l’application, elle est coupée là, avec un commentaire qui dit pourquoi. Ce fichier de la racine était encore celui qu’avait créé create-next-app au début du projet, avec d’autres règles : l’éditeur relisait les paquets avec une liste que pnpm lint n’appliquait pas.
--max-warnings 0 vaut pour les deux passes : un paquet n’a pas droit à plus d’avertissements que l’application.
Ce que le premier passage a trouvé#
La seconde passe a signalé trois avertissements au premier lancement. Aucun n’était grave en lui-même ; chacun menait à du code qui ne servait plus.
Un import jamais utilisé (r2StorageAdapter.ts, avant)
import { getR2Client, getR2PresignClient, getR2Bucket } from "@backend/modules/storage/adapters/r2/client";import { parsePathToDestination } from "@backend/modules/media/services/parsePathToDestination";import { probeMediaDimensions } from "@backend/modules/media/services/mediaDerivatives.service";L’avertissement porte sur un import ; la question utile est : qui d’autre appelle cette fonction ? Personne. parsePathToDestination reconstituait la destination d’un fichier à partir de son seul chemin, pour un rattrapage historique des fichiers du stockage. Ce rattrapage a été supprimé avec Cloudinary ; le service, lui, était resté, avec ses 160 lignes et sa documentation qui décrivait un appelant disparu. Il est supprimé.
Deux paramètres jamais lus (virtual-path.ts, avant)
export function pickBackend(_mimeType?: string | null): StorageProvider { return 'r2';}export function pickBackendByExtension(_filename?: string): StorageProvider { return 'r2';}Ici le lint signalait des paramètres inutilisés. Les renommer ou les retirer aurait fait taire l’avertissement sans rien régler : la vraie question était encore celle de l’appelant, et ces deux fonctions n’en avaient aucun. Leur commentaire les présentait comme « le point unique de décision si un second stockage arrivait un jour » — un point de décision que personne ne consulte ne décide de rien. Si un second stockage arrive, la décision s’écrira là où elle sera lue.
La même revue a trouvé deux restes que le lint ne pouvait pas voir, faute de vérifier ces fichiers :
Fichier | Ce qu’il faisait | Pourquoi il est parti |
|---|---|---|
prisma/backfill-navigation.ts | Un rattrapage ponctuel des familles de disciplines, lancé à la main lors d’une ancienne migration. | Il lit une colonne family que la base n’a plus depuis la toute première migration (0_init) : il ne pouvait qu’échouer. |
La clé prisma.seed du package.json | Désignait le script de remplissage lancé par prisma migrate reset. | Elle visait prisma/seed.js, qui n’existe pas ; la configuration de Prisma vit de toute façon dans prisma.config.ts. |
Showing entries1to2entries
Un avertissement de lint est un indice, pas une tâche. « Variable inutilisée » se corrige en une seconde en la retirant ; la bonne correction demande parfois de remonter jusqu’à la fonction entière, et de vérifier qu’elle a encore un appelant. Et supprimer du code mort exige une preuve : aucun appelant, une donnée qui n’existe plus.
Enfin, la liste include du web a perdu ses traces d’un ancien rangement (app, components, features… à la racine de apps/web, qui n’existent plus). Le changement est sans effet sur ce qui est vérifié, et c’est vérifiable : tsc --listFilesOnly donne exactement la même liste de fichiers avant et après.
Ce que pnpm check ne voit pas#
Toutes ces vérifications sont statiques : elles lisent le code sans l’exécuter. Elles trouvent un type incohérent, une règle de React enfreinte, une cible de tutoriel disparue. Elles ne voient pas :
Ce qui leur échappe | Exemple |
|---|---|
Une règle métier fausse mais bien typée | Une page brouillon montrée aux visiteurs : le code compile, il fait simplement la mauvaise chose. |
Ce qui dépend des données | Une requête correcte qui renvoie un document à qui ne devrait pas le voir, parce qu’un groupe a été mal rattaché. |
Ce qui dépend du temps | Un plan du site figé au démarrage du serveur (partie « Interface transverse ») : rien d’incohérent, juste un résultat qui vieillit. |
Le comportement dans un navigateur | Un menu qui ne s’ouvre pas au clavier, un formulaire qui s’envoie deux fois. |
Showing entries1to4entries
Ces erreurs-là se trouvent en exécutant le code : à la main, en relisant (ce qu’ont fait les revues de chaque partie de cette documentation), et par les tests de bout en bout du chapitre 4, lancés à part (pnpm e2e).
Récapitulatif#
Question | Réponse du projet |
|---|---|
Que lance pnpm check ? | Quatre gardes maison, l’effacement de .next, le typecheck des trois projets, le lint de l’application et des paquets. |
Dans quel ordre, et pourquoi ? | Les vérifications rapides et explicites d’abord ; la première qui échoue arrête tout. |
Pourquoi effacer .next ? | Le typecheck du web inclut tous les .ts du dossier, y compris les types générés par Next, qui peuvent être périmés. |
Quels projets TypeScript sont vérifiés ? | Le web, le backend et la configuration de la racine ; les contrats par le web et par le backend. |
Le lint relit-il le backend ? | Oui : une seconde passe, lancée depuis la racine avec la configuration du web, sans avertissement toléré. |
Que faire d’un avertissement de code inutilisé ? | Chercher l’appelant : souvent, c’est la fonction entière qui ne sert plus, et sa suppression demande une preuve. |
Que ne voit pas pnpm check ? | Ce qui ne se voit qu’à l’exécution : une règle métier fausse, les données, le temps, le navigateur. |
Showing entries1to7entries
À suivre#
Le typecheck et le lint sont les deux maillons les plus longs de la chaîne, et les plus instructifs : ramener le lint à zéro a demandé de réécrire seize composants selon les règles de React.
Chapitre suivant : le typecheck et le lint. Deux questions différentes, le lint ramené à zéro, et ce que les règles du React Compiler apprennent sur la façon d’écrire un composant.