pnpm dev sert un dist/ périmé : node --watch surveille le build, mais rien ne le reconstruit #16

Closed
opened 2026-09-09 20:47:35 +00:00 by nicolas-fryder · 2 comments

Constaté le 2026-09-09 sur le premier satellite réel monté depuis le template, en jouant une vérification de bout en bout. @eqs/* 0.7.0, Node 22.18, pnpm 11.8.0.

Le script dev d'apps/api :

"dev": "node --watch --env-file-if-exists=../../.env --env-file-if-exists=.env dist/main.js"

--watch surveille dist/. Mais rien ne compile les sources vers dist/. Il n'y a pas de tsc --watch en parallèle, pas de build en amont dans le script. Le serveur relancé sert donc le dernier dist/ qu'on a bien voulu produire — potentiellement vieux de plusieurs commits.

Ce qui s'est passé

Une vérification de bout en bout a échoué sur un 409 « Référence invalide » — un défaut réel, mais corrigé et committé plus tôt le même jour. L'API en marche servait un dist/ compilé avant ce correctif. Diagnostic : plusieurs minutes à chercher dans le code une régression qui n'existait pas.

Il a fallu tuer le processus, pnpm --filter api build, relancer. Là, ça marchait.

Reproduction

pnpm --filter api build
pnpm --filter api dev          # laisser tourner
# modifier une route dans src/, sauvegarder
# --watch ne redémarre pas (dist/ n'a pas changé), et l'ancien comportement persiste

Pourquoi ça compte plus qu'un simple confort

C'est la deuxième fois que le même piège se referme dans ce dépôt, sous une autre forme. eqs-platform#33 décrit eqs openapi export qui lit dist/openapi.js sans vérifier sa fraîcheur : on régénère un contrat qui décrit du code disparu, et --check compare au même dist/ périmé, donc la CI ne voit rien.

Même cause : plusieurs commandes du quotidien lisent dist/ en supposant qu'il est à jour, et aucune ne le vérifie. Le symptôme est toujours le même — le code qu'on lit n'est pas celui qui s'exécute — et c'est un des plus coûteux à diagnostiquer, parce qu'on cherche l'erreur dans la seule chose qui est juste : les sources.

Le cas est d'autant plus traître ici que --watch donne l'illusion inverse : on voit un drapeau de rechargement à chaud, on en conclut que les modifications sont prises. Un script sans --watch du tout aurait au moins été honnête.

Ce que ça coûte

Non bloquant, mais coûteux à chaque fois qu'il se produit, et il se produira à chaque personne, sur chaque satellite, la première fois qu'elle modifie une route et ne comprend pas pourquoi rien ne change.

Ce que je proposerais

Faire du script dev ce qu'il prétend être. Par exemple deux processus concurrents — tsc -w et node --watch dist/main.js — ou un lanceur qui compile à la volée. Le profil front (next dev) fait déjà ce qu'il faut ; c'est le profil nest qui décroche.

À défaut, au minimum : un pnpm --filter api build && en tête du script, pour que le démarrage parte au moins d'un état juste, et une ligne dans AGENTS.md disant que le rechargement à chaud ne recompile pas.

Plus largement, et c'est peut-être le vrai sujet : partout où le socle lit dist/, il gagnerait à comparer sa date à celle des sources et à le dire. C'est la même correction pour eqs-platform#33 et pour ce ticket.

Contournement en place

Aucun dans le dépôt — la consigne « reconstruire avant de relancer » est portée à la main, donc destinée à se perdre. C'est signalé dans le rapport de vérification du lot.

Constaté le 2026-09-09 sur le premier satellite réel monté depuis le template, en jouant une vérification de bout en bout. `@eqs/*` 0.7.0, Node 22.18, pnpm 11.8.0. Le script `dev` d'`apps/api` : ```json "dev": "node --watch --env-file-if-exists=../../.env --env-file-if-exists=.env dist/main.js" ``` `--watch` surveille `dist/`. **Mais rien ne compile les sources vers `dist/`.** Il n'y a pas de `tsc --watch` en parallèle, pas de `build` en amont dans le script. Le serveur relancé sert donc le dernier `dist/` qu'on a bien voulu produire — potentiellement vieux de plusieurs commits. ## Ce qui s'est passé Une vérification de bout en bout a échoué sur un `409 « Référence invalide »` — un défaut réel, mais **corrigé et committé plus tôt le même jour**. L'API en marche servait un `dist/` compilé avant ce correctif. Diagnostic : plusieurs minutes à chercher dans le code une régression qui n'existait pas. Il a fallu tuer le processus, `pnpm --filter api build`, relancer. Là, ça marchait. ## Reproduction ```bash pnpm --filter api build pnpm --filter api dev # laisser tourner # modifier une route dans src/, sauvegarder # --watch ne redémarre pas (dist/ n'a pas changé), et l'ancien comportement persiste ``` ## Pourquoi ça compte plus qu'un simple confort C'est la **deuxième fois** que le même piège se referme dans ce dépôt, sous une autre forme. `eqs-platform#33` décrit `eqs openapi export` qui lit `dist/openapi.js` sans vérifier sa fraîcheur : on régénère un contrat qui décrit du code disparu, et `--check` compare au même `dist/` périmé, donc la CI ne voit rien. Même cause : **plusieurs commandes du quotidien lisent `dist/` en supposant qu'il est à jour, et aucune ne le vérifie.** Le symptôme est toujours le même — le code qu'on lit n'est pas celui qui s'exécute — et c'est un des plus coûteux à diagnostiquer, parce qu'on cherche l'erreur dans la seule chose qui est juste : les sources. Le cas est d'autant plus traître ici que `--watch` **donne l'illusion inverse** : on voit un drapeau de rechargement à chaud, on en conclut que les modifications sont prises. Un script sans `--watch` du tout aurait au moins été honnête. ## Ce que ça coûte Non bloquant, mais coûteux à chaque fois qu'il se produit, et il se produira à chaque personne, sur chaque satellite, la première fois qu'elle modifie une route et ne comprend pas pourquoi rien ne change. ## Ce que je proposerais Faire du script `dev` ce qu'il prétend être. Par exemple deux processus concurrents — `tsc -w` et `node --watch dist/main.js` — ou un lanceur qui compile à la volée. Le profil `front` (`next dev`) fait déjà ce qu'il faut ; c'est le profil `nest` qui décroche. À défaut, au minimum : un `pnpm --filter api build &&` en tête du script, pour que le démarrage parte au moins d'un état juste, et une ligne dans `AGENTS.md` disant que le rechargement à chaud ne recompile pas. Plus largement, et c'est peut-être le vrai sujet : partout où le socle lit `dist/`, il gagnerait à comparer sa date à celle des sources et à le dire. C'est la même correction pour `eqs-platform#33` et pour ce ticket. ## Contournement en place Aucun dans le dépôt — la consigne « reconstruire avant de relancer » est portée à la main, donc destinée à se perdre. C'est signalé dans le rapport de vérification du lot.
Author
Owner

Vrai, et corrigé. Le script surveillait dist/ et rien ne le reconstruisait — il n'y avait ni tsc -w en parallèle, ni build en amont.

Vous mettez le doigt sur ce qui rend ce piège pire qu'un script sans surveillance : --watch affiche des redémarrages, donc on conclut que les modifications sont prises. Un script honnêtement muet aurait coûté moins cher.

tsc -w et node --watch tournent maintenant ensemble.

Deux impasses écartées, mesurées, pour que personne n'y retourne :

  • tsc -w & node --watch … : pnpm passe les scripts par cmd sous Windows, où & enchaîne au lieu de mettre en fond.
  • pnpm run --parallel "/^dev:/" : élégant et sans dépendance, mais récursif. Vérifié en posant un dev:essai dans apps/api et un autre dans apps/web puis en lançant depuis apps/api — les deux sont partis. Inutilisable pour deux scripts d'un même package.

Reste concurrently, en dépendance de développement d'apps/api.

Vérifié de bout en bout, pas seulement en lisant le script : dev lancé, une route modifiée dans src/, puis constaté que dist/ping/ping.controller.js a une date plus récente, contient la modification, et que les deux processus ont réagi :

[types] 23:06:50 - Found 0 errors. Watching for file changes.
[serveur] Restarting 'dist/main.js'

Sur votre point le plus large — « partout où le socle lit dist/, il gagnerait à comparer sa date » : d'accord, et c'est fait pour le cas que vous citez. eqs openapi export refuse désormais un point d'entrée plus ancien que les sources, en nommant le fichier qui a bougé (D-042). Vous aviez raison de rapprocher les deux tickets : même cause, le code qu'on lit n'est pas celui qui s'exécute.

Il reste un troisième endroit du même genre, et je ne le cache pas : rien ne vérifie qu'un client généré committé est à jour de son contrat. Le geste est écrit dans l'orval.config.mjs du gabarit, il n'est dans aucune porte.

Branche fix/dev-recompile. AGENTS.md dit maintenant que pnpm dev recompile — la phrase était fausse, elle devient vraie.

Vrai, et corrigé. Le script surveillait `dist/` et rien ne le reconstruisait — il n'y avait ni `tsc -w` en parallèle, ni `build` en amont. Vous mettez le doigt sur ce qui rend ce piège pire qu'un script sans surveillance : `--watch` **affiche des redémarrages**, donc on conclut que les modifications sont prises. Un script honnêtement muet aurait coûté moins cher. `tsc -w` et `node --watch` tournent maintenant ensemble. **Deux impasses écartées, mesurées, pour que personne n'y retourne :** - `tsc -w & node --watch …` : pnpm passe les scripts par `cmd` sous Windows, où `&` enchaîne au lieu de mettre en fond. - `pnpm run --parallel "/^dev:/"` : élégant et sans dépendance, mais **récursif**. Vérifié en posant un `dev:essai` dans `apps/api` et un autre dans `apps/web` puis en lançant depuis `apps/api` — les deux sont partis. Inutilisable pour deux scripts d'un même package. Reste `concurrently`, en dépendance de développement d'`apps/api`. **Vérifié de bout en bout**, pas seulement en lisant le script : `dev` lancé, une route modifiée dans `src/`, puis constaté que `dist/ping/ping.controller.js` a une date plus récente, contient la modification, et que les deux processus ont réagi : ``` [types] 23:06:50 - Found 0 errors. Watching for file changes. [serveur] Restarting 'dist/main.js' ``` **Sur votre point le plus large — « partout où le socle lit `dist/`, il gagnerait à comparer sa date »** : d'accord, et c'est fait pour le cas que vous citez. `eqs openapi export` **refuse** désormais un point d'entrée plus ancien que les sources, en nommant le fichier qui a bougé (D-042). Vous aviez raison de rapprocher les deux tickets : même cause, le code qu'on lit n'est pas celui qui s'exécute. Il reste un troisième endroit du même genre, et je ne le cache pas : rien ne vérifie qu'un client généré committé est à jour de son contrat. Le geste est écrit dans l'`orval.config.mjs` du gabarit, il n'est dans aucune porte. Branche `fix/dev-recompile`. `AGENTS.md` dit maintenant que `pnpm dev` recompile — la phrase était fausse, elle devient vraie.
Author
Owner

Vérifié — corrigé, et c'était la moitié qui manquait

Repris le 2026-09-10 sur le satellite qui avait ouvert ce ticket. Nous avons pris votre version du script dev d'apps/api : il recompile désormais réellement.

Pourquoi ça comptait

Ce défaut nous a coûté du temps deux fois, et la seconde était la pire : une vérification de bout en bout échouait sur un 409 « Référence invalide » — un défaut réel, mais corrigé et committé le matin même. L'API en marche servait un dist compilé avant le correctif. On cherche alors la régression dans la seule chose qui est juste : les sources.

Ce qui le rendait particulièrement traître, c'est que --watch donne l'illusion inverse. On voit un drapeau de rechargement à chaud, on en conclut que les modifications sont prises. Un script sans --watch du tout aurait au moins été honnête.

Le rapprochement avec eqs-platform#33

Les deux avaient la même cause : des commandes qui lisent dist/ en le supposant frais, sans jamais le vérifier. Vous aviez noté que la vraie réponse serait, partout où le socle lit dist/, de comparer sa date à celle des sources et de le dire.

C'est fait des deux côtés maintenant, et le résultat est net. eqs openapi export --check refuse un dist périmé en nommant le fichier changé et en sortant en 1 (vérifié, commenté sur #33), et le script dev recompile. Les deux moitiés du même problème sont fermées.

C'est le genre de correction dont on ne mesure la valeur qu'a posteriori : le symptôme « le code que je lis n'est pas celui qui s'exécute » est un des plus coûteux à diagnostiquer, parce qu'il n'oriente vers rien.

De notre côté, c'est refermé.

## Vérifié — corrigé, et c'était la moitié qui manquait Repris le 2026-09-10 sur le satellite qui avait ouvert ce ticket. Nous avons pris votre version du script `dev` d'`apps/api` : il recompile désormais réellement. ### Pourquoi ça comptait Ce défaut nous a coûté du temps **deux fois**, et la seconde était la pire : une vérification de bout en bout échouait sur un `409 « Référence invalide »` — un défaut réel, mais corrigé et committé le matin même. L'API en marche servait un `dist` compilé avant le correctif. On cherche alors la régression dans la seule chose qui est juste : les sources. Ce qui le rendait particulièrement traître, c'est que `--watch` **donne l'illusion inverse**. On voit un drapeau de rechargement à chaud, on en conclut que les modifications sont prises. Un script sans `--watch` du tout aurait au moins été honnête. ### Le rapprochement avec `eqs-platform#33` Les deux avaient la même cause : des commandes qui lisent `dist/` en le supposant frais, sans jamais le vérifier. Vous aviez noté que la vraie réponse serait, partout où le socle lit `dist/`, de comparer sa date à celle des sources et de le dire. **C'est fait des deux côtés maintenant**, et le résultat est net. `eqs openapi export --check` refuse un `dist` périmé en nommant le fichier changé et en sortant en 1 (vérifié, commenté sur `#33`), et le script `dev` recompile. Les deux moitiés du même problème sont fermées. C'est le genre de correction dont on ne mesure la valeur qu'a posteriori : le symptôme « le code que je lis n'est pas celui qui s'exécute » est un des plus coûteux à diagnostiquer, parce qu'il n'oriente vers rien. De notre côté, c'est refermé.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-satellite-template#16
No description provided.