@nodefony/devkit 10.0.0-alpha.6 → 10.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.149.0 → _@oxc-project_runtime@0.150.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.149.0 → _@oxc-project_runtime@0.150.0}/helpers/esm/decorateMetadata.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.149.0 → _@oxc-project_runtime@0.150.0}/helpers/esm/decorateParam.js +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/nodefony/controllers/DevkitController.js +2 -2
  6. package/dist/nodefony/controllers/McpController.js +3 -3
  7. package/dist/nodefony/service/DevkitService.js +2 -2
  8. package/docs/index.md +2 -2
  9. package/package.json +8 -8
  10. package/skills/nodefony-add-crud/SKILL.md +11 -2
  11. package/skills/nodefony-add-realtime-channel/SKILL.md +9 -0
  12. package/skills/nodefony-add-service/SKILL.md +9 -0
  13. package/skills/nodefony-browser/SKILL.md +31 -23
  14. package/skills/nodefony-dev/SKILL.md +497 -0
  15. package/skills/nodefony-dev/scripts/docs.mjs +544 -0
  16. package/skills/nodefony-devops/SKILL.md +198 -0
  17. package/skills/nodefony-devops/references/compose.md +125 -0
  18. package/skills/nodefony-devops/references/frontal.md +119 -0
  19. package/skills/nodefony-devops/references/image.md +136 -0
  20. package/skills/nodefony-devops/references/kubernetes.md +189 -0
  21. package/skills/nodefony-devops/references/podman.md +103 -0
  22. package/skills/nodefony-devops/references/secrets.md +108 -0
  23. package/skills/nodefony-devops/references/variables.md +174 -0
  24. package/skills/nodefony-migrate-schema/SKILL.md +50 -17
  25. package/skills/nodefony-protect-route/SKILL.md +78 -0
@@ -0,0 +1,497 @@
1
+ ---
2
+ name: nodefony-dev
3
+ description: >
4
+ Conduit une tâche de développement de bout en bout dans une application Nodefony — comprendre
5
+ le code en place, choisir la bonne façade, générer plutôt qu'écrire à la main, retrouver la
6
+ référence installée qu'une recherche ordinaire ne voit pas, puis prouver que c'est fait — et se
7
+ charge AVANT la première modification, quelle que soit la tâche.
8
+ Les gestes spécialisés ont leur propre skill (ressource REST, service, canal temps réel, garde
9
+ de route, migration de schéma, écran vu au navigateur) ; celui-ci porte la conduite commune,
10
+ les pièges du serveur et du front, et dit lequel prendre.
11
+ Déclencheurs : "je veux ajouter une fonctionnalité", "comment on code dans ce framework",
12
+ "par où je commence", "où est la doc de ça", "comment ça marche ici", "avant de coder",
13
+ "est-ce que j'écris ça à la main", "comment vérifier que c'est bon", "mon changement
14
+ est-il fini", "je ne trouve rien sur ce sujet", "ce n'est pas documenté", "je touche au
15
+ frontend", "mon composant charge des données", "mon écran n'affiche rien".
16
+ metadata:
17
+ version: 1.2.0
18
+ ---
19
+
20
+ # nodefony-dev — développer dans cette application sans rien inventer
21
+
22
+ > **Maintenance** : vérité courante, jamais un journal. Éditer en place ; l'historique vit dans
23
+ > `git log`. Ce fichier n'a **pas** de `references/` — et c'est une décision mesurée, pas un
24
+ > oubli : sur 1020 transcripts d'agents lâchés dans une application générée, une page de
25
+ > `references/` a été ouverte **0 fois**, quand un script cité comme commande l'a été dans 80 %
26
+ > des cas. Ce qui doit atteindre un agent s'EXÉCUTE ou vit dans ce corps ; ce qu'on déporterait
27
+ > « pour alléger » n'atteindrait plus personne.
28
+
29
+ ## Passer la main — TON PREMIER GESTE
30
+
31
+ 🔴 **Ta tâche est dans cette table ? CHARGE ce skill MAINTENANT, avant d'écrire une ligne, avant
32
+ même d'ouvrir un fichier.** Ce n'est pas un conseil de lecture : c'est le premier geste de la
33
+ tâche. Reviens ici ensuite si tu en as encore besoin.
34
+
35
+ **Ce que coûte de ne pas le faire, mesuré au banc, six exécutions :** chaque agent qui a chargé le
36
+ skill de sa tâche a réussi ; **chacun de ceux qui ne l'ont pas chargé a échoué** — l'un en
37
+ cherchant la base de l'application au mauvais endroit puis en l'effaçant, l'autre en écrivant une
38
+ migration qu'il n'a jamais appliquée. Aucun ne manquait d'information : la table était sous leurs
39
+ yeux. Ils ont simplement continué sans elle.
40
+
41
+ Ces skills sont installés avec le framework et se chargent par leur nom. Chacun porte les pièges
42
+ de son geste, et ceux-là ne se devinent pas — ils se paient.
43
+
44
+ | Ce que tu t'apprêtes à faire | Le skill |
45
+ | -------------------------------------------------------------------- | ------------------------------- |
46
+ | Une ressource : table, validation, service, REST et WebSocket | `nodefony-add-crud` |
47
+ | Une logique métier réutilisable, hors de tout controller | `nodefony-add-service` |
48
+ | Un flux temps réel, un canal, un abonnement client | `nodefony-add-realtime-channel` |
49
+ | Réserver une route, un rôle, une zone, ouvrir à un partenaire | `nodefony-protect-route` |
50
+ | Changer une entité déjà en base, ou déployer un schéma changé | `nodefony-migrate-schema` |
51
+ | Conclure quoi que ce soit d'un ÉCRAN — affichage, contraste, console | `nodefony-browser` |
52
+
53
+ Aucune ligne ne correspond ? Alors seulement, continue ici. Et si ta tâche est EXACTEMENT celle
54
+ d'un spécialiste, va droit à lui : il n'y a pas à passer par cette page d'abord.
55
+
56
+ `ls .agents/skills/` liste ceux que TON projet a reçus ; `npx nodefony ai:sync` les remet à jour
57
+ après une montée de version. Ce sont des **pointeurs** : le contenu vit dans `node_modules` et
58
+ suit la version installée — les éditer ne servirait à rien.
59
+
60
+ ## 1. La règle qui gouverne tout
61
+
62
+ **N'invente jamais du code Nodefony : génère-le, imite-le, vérifie-le.**
63
+
64
+ Ton `AGENTS.md` porte les trois actes et les tables qui vont avec — les générateurs, les
65
+ vérités du framework, les gates. **Ce skill ne les recopie pas** : une règle écrite à deux
66
+ endroits diverge au premier changement, et c'est alors la copie qu'on lit. Il porte ce qui n'y
67
+ tient pas : la **conduite** d'une tâche, les pièges qui coûtent une heure, et l'outil qui répond
68
+ à « où est-ce documenté ? ».
69
+
70
+ Le réflexe, avant d'écrire le moindre fichier : **un générateur le produit-il ?**
71
+ `npx nodefony create --help` liste ceux de TA version — la liste s'allonge, ta mémoire non.
72
+
73
+ ## 2. Trouver la référence — ce que `rg` ne peut pas voir
74
+
75
+ C'est le trou le plus coûteux de ce framework, et il ne ressemble pas à un trou : **la
76
+ documentation est installée, complète, et invisible**. `rg "session"` lancé à la racine ne
77
+ descend pas dans `node_modules` (git l'ignore, `rg` le suit). Le sujet paraît absent alors qu'il
78
+ occupe quinze pages — mesuré sur une application générée : **70 pages, plus de 38 000 lignes**.
79
+
80
+ Conclure « ce n'est pas documenté » et réécrire à la main est l'erreur que ce script existe pour
81
+ empêcher :
82
+
83
+ ```bash
84
+ D=node_modules/@nodefony/devkit/skills/nodefony-dev/scripts/docs.mjs
85
+
86
+ node $D session cookie # les pages qui répondent — chemin, LIGNE, extrait
87
+ node $D --list # tout ce qui est installé, par module
88
+ node $D --open firewall # le chemin d'une page, pour l'ouvrir
89
+ node $D upload --json # pour rechaîner
90
+ ```
91
+
92
+ Il ne stocke aucune documentation : il lit celle de TES paquets à l'exécution, et rend le chemin
93
+ **et la ligne**. Tu ouvres à l'endroit exact au lieu de relire une page entière. Classement par
94
+ titre, sujet, étiquettes puis corps — une ligne qui porte TOUS tes termes passe devant deux
95
+ lignes qui en portent un chacune ; les accents sont ignorés.
96
+
97
+ **Ses codes de sortie disent quoi faire**, et l'un d'eux compte plus que les autres :
98
+
99
+ | Code | Ce qu'il veut dire | Le geste |
100
+ | ---: | ----------------------- | ------------------------------------------------------------- |
101
+ | 0 | trouvé | ouvrir le fichier à la ligne rendue |
102
+ | 1 | rien sur ces termes | un seul mot, ou `--list` pour voir les sujets couverts |
103
+ | 78 | **rien n'est installé** | `npm install` — ce n'est PAS « le sujet n'est pas documenté » |
104
+ | 64 | drapeau inconnu | `--help` |
105
+
106
+ > 🔴 **`78` n'est pas une panne, c'est une information.** Un projet dont les dépendances ne sont
107
+ > pas installées n'a aucune documentation à lire. Le DIRE ; ne jamais réécrire de mémoire ce
108
+ > qu'on n'a pas pu lire.
109
+
110
+ Deux autres voies existent quand l'application **tourne** : l'outil MCP `nodefony_docs` (cherche
111
+ dans la doc chargée) et `nodefony_symbols` (rend la SIGNATURE réelle d'un symbole, que ce script
112
+ ne porte pas). Elles supposent un serveur démarré et la porte câblée (`npx nodefony ai:mcp`) ;
113
+ `docs.mjs`, lui, répond toujours.
114
+
115
+ ## 3. Interroger l'application — elle répond mieux que ses sources
116
+
117
+ **Perdu ? La carte de visite dit qui répond, ce qui est chargé, où lire et quoi lancer :**
118
+
119
+ ```bash
120
+ npx nodefony card # -j pour du JSON
121
+ ```
122
+
123
+ Elle répond **toujours** — application pas encore construite, terminal sans aucune variable
124
+ d'environnement : elle ne lit que des fichiers. Elle le DIT alors (« modules installés », pas
125
+ « chargés ») et renvoie à `inspect modules` pour ce qui est vraiment monté.
126
+
127
+ ```bash
128
+ npx nodefony inspect routes --json # routes réelles (chemin MONTÉ, méthodes, controller)
129
+ npx nodefony inspect services --json # services enregistrés, et le module qui les porte
130
+ npx nodefony inspect config --json # config EFFECTIVE, et d'où vient chaque valeur
131
+ npx nodefony inspect schema http # ce qu'on a le DROIT d'écrire : clés, type, défaut, description
132
+ npx nodefony inspect modules --json # modules CHARGÉS — pas ceux que le manifeste déclare
133
+ npx nodefony inspect entities --json # entités déclarées à l'ORM
134
+ npx nodefony inspect stores --json # où sont RÉELLEMENT écrites les données (sessions, cache…)
135
+ npx nodefony inspect graph --json # graphe des entités et de leurs relations
136
+ ```
137
+
138
+ Ces commandes bootent l'application **sans ouvrir un seul port** et rendent exactement ce que sert
139
+ la console d'administration — même code, deux portes.
140
+
141
+ > **Ce que rend `inspect` ENGLOBE tes sources et les dépasse.** Les modules installés montent leurs
142
+ > propres routes : une application qui en définit une poignée en expose couramment plus d'une
143
+ > centaine. Un écart d'un ordre de grandeur entre tes fichiers et `inspect routes --json | jq
144
+ 'length'` n'est PAS une anomalie de l'outil — c'est la différence entre ce que TU as écrit et ce
145
+ > que l'application MONTE.
146
+
147
+ **« Que fait cette classe, où est-elle définie, qu'étend-elle ? » — une commande, pas une fouille :**
148
+
149
+ ```bash
150
+ npx nodefony symbols AbstractCrudService # définition, TSDoc, parenté — en O(1)
151
+ npx nodefony symbols --module @nodefony/http # toute la surface exportée d'un paquet
152
+ ```
153
+
154
+ Le graphe symbolique de tout le framework est livré avec le paquet `nodefony` : la réponse ne
155
+ dépend ni d'un serveur, ni d'un build, ni de ta connexion. Va y chercher un symbole AVANT d'ouvrir
156
+ un `.d.ts` — et avant, surtout, d'inventer une signature.
157
+
158
+ **Si la commande te résiste, répare l'APPEL — ne te rabats pas sur les sources.** C'est le réflexe
159
+ le plus cher, parce qu'il produit une réponse d'allure normale : un shell qui manque un outil
160
+ (`timeout` n'existe pas sur macOS), un `jq` mal formé, et l'on se replie sur ce qu'on sait lire.
161
+ Les fichiers répondront toujours quelque chose — mais pas à la question posée. Relance sans le
162
+ tube, puis remets ton filtre.
163
+
164
+ **Tu préfères des OUTILS à des commandes ?** Cette application les expose en Model Context Protocol
165
+ (`npx nodefony ai:mcp` écrit `.mcp.json`) — mêmes réponses, et tes propres modules peuvent publier
166
+ les leurs. La porte est une ROUTE : elle n'existe que serveur démarré, et un client qui la trouve
167
+ éteinte la marque en échec pour toute sa session — démarre l'application D'ABORD, ta session
168
+ ENSUITE. Tout le reste (déclarer un outil métier, le réserver à des scopes, l'autorisation OAuth) :
169
+ `node_modules/@nodefony/devkit/docs/index.md`.
170
+
171
+ ### L'environnement : ne devine JAMAIS, demande
172
+
173
+ ```bash
174
+ npx nodefony env # cascade des .env, valeur EFFECTIVE de chaque variable, sa PROVENANCE
175
+ ```
176
+
177
+ **Encadre toute modification de configuration par cette commande** : une fois AVANT, pour savoir ce
178
+ qui s'applique et d'où ça vient ; une fois APRÈS, pour prouver que ta valeur est celle qui gagne.
179
+ Lire les `.env` toi-même donne des contenus ; la précédence est un mécanisme — tu ne peux que la
180
+ supposer, et une supposition fausse ne se voit qu'en production. La commande ne boote rien, donc
181
+ elle répond aussi quand l'application ne démarre plus.
182
+
183
+ **Précédence, du plus FORT au plus faible** — le premier qui pose une valeur gagne, les suivants
184
+ sont ignorés en silence :
185
+
186
+ ```
187
+ process.env > .env.<déploiement>.local > .env.<mode>.local > .env.local
188
+ > .env.<déploiement> > .env.<mode> > .env
189
+ ```
190
+
191
+ `<mode>` = `NODE_ENV` · `<déploiement>` = `APP_ENV` (plus spécifique, donc plus fort). Les `*.local`
192
+ ne sont jamais committés : les secrets y vont, et nulle part ailleurs.
193
+
194
+ | Forme | Ce que c'est | Où c'est déclaré |
195
+ | ------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------ |
196
+ | `NF_PORT=5151` | variable de l'APP, typée et validée | `env.ts` (`defineEnv`) — non déclarée = **sans effet** |
197
+ | `NF__HTTP__SERVERS__HTTPS__PORT=8443` | surcharge DIRECTE d'une clé de config d'un module | rien à déclarer — double `__` = séparateur |
198
+ | `NF_TOTP_KEY_FILE=/run/secrets/x` | la même variable, lue depuis un fichier (secret K8s) | idem `NF_TOTP_KEY` |
199
+
200
+ Une variable `NF_` mal orthographiée n'échoue pas : elle est **ignorée**, et le défaut s'applique en
201
+ silence. `npx nodefony env` est le seul endroit qui la montre, avec la correction probable.
202
+
203
+ ## 4. Les commandes — demande la liste, ne la devine pas
204
+
205
+ ```bash
206
+ npx nodefony --help # TOUTES les commandes, celles des modules installés comprises
207
+ npx nodefony <commande> --help # les options exactes de l'une d'elles
208
+ ```
209
+
210
+ La liste **dépend des modules installés** : elle n'est pas la même d'une application à l'autre, et
211
+ elle s'allonge dès que tu en ajoutes un. C'est pour ça qu'elle se demande au lieu de se retenir.
212
+
213
+ **Toujours `npx`, jamais `nodefony` nu.** Le binaire vit dans les `node_modules` de CETTE
214
+ application : la forme nue rend un code 127 tant que rien n'est installé globalement. Une
215
+ installation globale existe (elle sert à créer une application HORS projet) et, dans un projet, elle
216
+ passe la main au binaire local — mais elle peut être plus ANCIENNE. `npx` prend la version que cette
217
+ application a choisie, sans dépendre de ce qui traîne sur la machine.
218
+
219
+ Celles qu'on n'invente pas, faute de savoir qu'elles existent :
220
+
221
+ - Mettre l'application derrière **nginx ou haproxy** — `npx nodefony proxy:generate <nginx|haproxy>`
222
+ - **Servir les fichiers statiques depuis un CDN** — `npx nodefony assets:publish [--clean]`
223
+ - **Certificat TLS de développement** — `npx nodefony http:certificates`
224
+ - **Dépendances en retard**, agrégées et non le brut de npm — `npx nodefony outdated`
225
+ - **Cohérence du projet** (classe non câblée, route qui répondra 404) — `npx nodefony doctor`
226
+ - **Plusieurs processus, un cœur chacun** — `npx nodefony production -w <n|auto>` · `NF_WORKERS`
227
+ - **Complétion au TAB** — `source <(nodefony completion zsh)`
228
+
229
+ Ce tableau ne remplace pas `--help` : lui seul connaît les modules de CETTE application, et il fait
230
+ foi le jour où les deux divergent.
231
+
232
+ ## 5. Piloter le serveur — et l'ARRÊTER
233
+
234
+ ```bash
235
+ npm run dev # développement : rechargement auto, Ctrl+C pour arrêter
236
+ npx nodefony development --no-watch # développement SANS rechargement : un process, stable
237
+ npx nodefony status # que tourne-t-il ? ports, PID — ne boote rien
238
+ npx nodefony stop # arrêt PROPRE de tout runtime de cette application
239
+ npx nodefony stop <nom|chemin> # arrêter un AUTRE projet, sans changer de dossier
240
+ npx nodefony production --detach --wait # boot réel en arrière-plan ; rend la main ports OUVERTS
241
+ ```
242
+
243
+ **Arrête ce que tu démarres.** Un serveur laissé derrière garde les ports : le run suivant échoue
244
+ sur une erreur qui ne parle jamais de lui (`EADDRINUSE`) — ou pire, un test interroge l'ANCIENNE
245
+ version du code. Et **jamais `… &`** : le processus reçoit SIGHUP et meurt ; tuer le PID du port ne
246
+ tue pas le superviseur, qui respawne.
247
+
248
+ **Ces commandes ne voient QUE cette application.** Plusieurs projets Nodefony peuvent tourner sur la
249
+ même machine ; `status` ne compte jamais les processus du voisin comme les tiens, il les NOMME dans
250
+ une table à part — et ce nom est ce que `stop` accepte. Donc « aucune instance » veut dire « aucune
251
+ À MOI », pas « rien ne tourne » ; et une cible que `stop` ne peut pas désigner sans ambiguïté est
252
+ REFUSÉE, avec un code de sortie non nul et rien d'arrêté — **lis ce code**, un refus ressemble
253
+ sinon à un succès.
254
+
255
+ **Pour faire tourner une suite contre un serveur, prends `--no-watch`.** Le mode développement
256
+ relance le serveur dès qu'un fichier bouge : pendant un run, le redémarrage coupe les connexions
257
+ sous les tests, et le rouge qui en sort accuse le code alors que le fautif est le décor.
258
+
259
+ **N'invente pas d'attente** : `--wait` ne rend la main qu'une fois les ports en écoute — un `sleep`
260
+ arbitraire est soit trop court (test rouge sans raison), soit du temps perdu à chaque exécution.
261
+
262
+ ## 6. Conduire une tâche — la séquence, et ses points d'arrêt
263
+
264
+ 1. **Demande à l'application, ne déduis pas du code** (§3). Une route lue dans un fichier peut
265
+ n'être montée nulle part ; l'inverse aussi. **Un argument RESTREINT la réponse** — `inspect
266
+ routes auth` ne rend que les routes dont le chemin, le nom, le contrôleur, l'action, le module
267
+ ou les méthodes portent `auth`. Ne tronque JAMAIS une sortie d'inspection (`| head`) pour la
268
+ faire tenir : ce qu'on cherche est presque toujours dans la partie coupée — c'est ainsi qu'on
269
+ conclut « le framework ne fournit pas ça » et qu'on le réécrit à la main.
270
+ 2. **Cherche la référence** (§2) avant de choisir une façade. Le framework en a presque toujours
271
+ une, et la contourner compile — c'est tout le piège.
272
+ 3. **Génère.** Si un générateur couvre le besoin, lance-le et **imite sa sortie** pour le reste.
273
+ `--dry-run` montre le plan et les diffs sans rien écrire ; un refus n'écrit jamais rien.
274
+ 4. **Édite le moins possible**, et regroupe : toutes les modifications serveur d'une même
275
+ fonctionnalité, PUIS un seul cycle de reconstruction. Le frontend passe en HMR, zéro
276
+ redémarrage.
277
+ 5. **Prouve.** `npm run verify` — une seule commande : types, style, tests, câblage, dans cet
278
+ ordre. Elle s'arrête au premier rouge, et **ce rouge est ta tâche suivante**.
279
+
280
+ **Le point d'arrêt qu'on rate** : `npm test` seul ne prouve pas que ça compile — vitest
281
+ n'inspecte aucun type. Une application peut être verte et ne pas compiler.
282
+
283
+ ### Le poids du modèle est un CHOIX, et il est mesuré
284
+
285
+ Si ton outil sait déléguer à des sous-agents : une tâche couverte par un **générateur** ne demande
286
+ pas un gros modèle — c'est le générateur qui porte le savoir. Mesuré sur ce framework, « ajoute une
287
+ ressource REST » rend le MÊME résultat en modèle léger et en modèle fort (mêmes contrôles verts,
288
+ écart d'étapes dans le bruit) pour **~3× moins cher**. À l'inverse, le socle SANS générateur (flux,
289
+ session, cycle de vie) fait échouer le modèle léger environ une fois sur deux.
290
+
291
+ Donc : **léger** pour appeler un générateur, inventorier, lire, vérifier un fait, appliquer un
292
+ patron ; **fort** pour écrire du socle sans générateur et pour arbitrer une architecture. Le test
293
+ qui tranche en une seconde : _la tâche a-t-elle une bonne réponse vérifiable ?_ Aucun nom de modèle
294
+ ici — ils changent tous les trimestres ; raisonne en poids.
295
+
296
+ ## 7. Les pièges du serveur
297
+
298
+ Chacun a déjà coûté au moins une heure à quelqu'un. Les quatre premiers sont les plus fréquents.
299
+
300
+ - **Ta route répond 404 alors qu'elle existe dans les sources.** Le runtime charge `dist/`, pas
301
+ le source : `npm run build`. C'est la cause n°1.
302
+ - **Une classe que rien ne déclare compile, passe ses tests, et casse au démarrage suivant** —
303
+ entité hors `@entities([…])`, controller hors `@controllers([…])`. Table jamais créée, route
304
+ en 404. C'est le mode d'échec de la COPIE : on recopie le voisin au lieu d'appeler le
305
+ générateur, qui, lui, déclare. `npx nodefony doctor` la NOMME.
306
+ - **Une clé de configuration mal orthographiée est retirée en silence.** `satisfies` sur le
307
+ fragment n'est pas décoratif : sans lui, la faute compile, puis la validation écarte la clé et
308
+ le module démarre sur son défaut. Personne ne le voit.
309
+ - **Tu lis une liste sans la BORNER.** Un `find` sans limite matérialise la table entière —
310
+ indolore sur les quelques lignes du poste de développement, fatal sur les dizaines de milliers
311
+ de la production. Le service d'une entité hérite `findPage({ limit: 25 })`.
312
+
313
+ - **Des dizaines de tests d'intégration rouges d'un coup** (`ECONNREFUSED`) : ils FRAPPENT un
314
+ serveur, ils ne le lancent pas — il est éteint. `npx nodefony status` d'abord ; en e2e, laisse
315
+ la commande gérer le cycle.
316
+ - **Ta route NEUVE répond 404 et le `dist/` est à jour** : elle n'est pas montée où tu crois. Le
317
+ chemin réel est le PRÉFIXE de son controller suivi du `path` de la route — une action
318
+ `path: "/widget"` dans un controller `@controller("/api")` répond sur `/api/widget`.
319
+ `npx nodefony inspect routes --json` donne le chemin MONTÉ.
320
+ - **TOUT répond 404, même les routes du gabarit** : un AUTRE serveur tient les ports — ou LE TIEN
321
+ a glissé, le port voulu étant pris. `npx nodefony status` montre les ports RÉELS, pas ceux que
322
+ tu as configurés, et NOMME le projet voisin.
323
+ - 🔴 **`nodefony <commande>` échoue là où `npm run dev` réussit, sur la MÊME application** : DEUX
324
+ paquets `nodefony` tournent dans le processus — un binaire lié ailleurs (installation globale,
325
+ `npm link`) exécute un noyau pendant que l'application importe le sien. Chaque copie a ses
326
+ propres classes, son contexte de requête et ses registres d'injection : un module construit à la
327
+ frontière perd son container SANS la moindre erreur — il figure dans la liste des modules et ne
328
+ fait rien. ⚠️ Les deux copies peuvent être la MÊME version ; deux fichiers distincts suffisent,
329
+ il n'y a aucune incohérence à repérer. `npm ls nodefony` liste les copies (« deduped » partout =
330
+ une seule) ; `NF_CLI_DEBUG=1 nodefony --version` dit quel CLI s'exécute.
331
+ - **`localhost` et `127.0.0.1` te jouent des tours** : ce sont deux ORIGINES distinctes — cookies,
332
+ cache et passkeys ne les partagent pas. Une seule origine en développement, partout, URL ouverte
333
+ comme callbacks.
334
+
335
+ - **Les routes authentifiées plafonnent quand le reste tient la charge** : le stockage de session
336
+ par défaut est SYNCHRONE — chaque reprise bloque la boucle d'événements. Compare une route
337
+ anonyme et une route authentifiée AVANT d'accuser TLS ou le pare-feu ; passe le stockage sur
338
+ redis pour la charge.
339
+
340
+ ### Ce qui tue le processus, pas la requête
341
+
342
+ - 🔴 **Une promesse lancée sans `await` porte TOUJOURS un `.catch()`.** Audit, courriel,
343
+ notification, nettoyage différé : un rejet non capté ne casse pas la requête, il tue le
344
+ **processus entier** — donc toutes les requêtes en vol, pas seulement la tienne.
345
+ - 🔴 **Un handler ne bloque jamais la boucle d'événements.** Aucune API `*Sync`
346
+ (`readFileSync`, `pbkdf2Sync`, `zlib`, `child_process`), aucun `JSON.parse` sur un corps non
347
+ borné, aucune expression régulière à quantificateurs imbriqués sur une entrée utilisateur.
348
+ Au-delà d'une milliseconde de calcul, passe par `worker_threads` : un seul handler lourd
349
+ bloque **tous** les clients, pas seulement celui qui l'a déclenché.
350
+ - **Un `PATCH` au corps vide se refuse en `400`.** Le laisser passer produit un `updateOne({})`,
351
+ qui finit en `500 « No values to set »` — une erreur serveur pour une faute de client.
352
+
353
+ ### Quand ça ne va pas, et que le message ne suffit pas
354
+
355
+ - **Un test vert seul et rouge en suite accuse une RESSOURCE PARTAGÉE**, pas ton code : base ou
356
+ index réutilisé, port, store jamais purgé, assertion `count === N` qui suppose une table
357
+ vierge. Lance deux fichiers ENSEMBLE pour isoler la paire, puis cloisonne. **Ne sérialise
358
+ jamais la suite** pour faire passer le rouge : tu masques la cause et tu paies la lenteur à
359
+ chaque exécution.
360
+ - **Une suite lancée contre un serveur en `production` reçoit `404` partout** : les modules
361
+ `policy:"dev"` n'y sont pas. `NF_WITH_DEV_MODULES=1` déroge pour 30 minutes
362
+ (`NF_WITH_DEV_MODULES_TTL_MIN`, 4 h au plus), et le `CRITIC « arrêt automatique … dérogation »`
363
+ du journal est cette garde qui se referme — pas une panne.
364
+ - **Ton application a démarré AMPUTÉE et tout a l'air sain** — base injoignable, module écarté
365
+ par sa politique. Seul `npx nodefony doctor` le dit, en relisant `var/last-boot.json`.
366
+ ⚠️ Ce bilan a un ÂGE : il décrit le **dernier démarrage**, et une commande console (`inspect`,
367
+ une commande de module) ne l'écrase pas. C'est voulu — lis la date avant d'en conclure.
368
+ - **L'application ne démarre plus et le superviseur avale la sortie** :
369
+ `NF_DEV_CHILD=1 npx nodefony development` lance l'enfant seul et montre le crash brut.
370
+
371
+ ## 8. Données et fichiers — les façades qu'on ne recompose pas
372
+
373
+ - **Un adaptateur de données ne remplace pas l'autre : ils se COMPLÈTENT.** Chacun déclare les
374
+ _stores_ qu'il sait tenir (`nodefony.stores` de son `package.json`) — `drizzle` les huit,
375
+ `mongoose` cinq (ni `totp`, ni `audit`, ni `idempotency`), `redis` quatre. Ce n'est pas un retard
376
+ de développement mais un CHOIX : un journal d'audit n'a rien à faire dans un moteur documentaire.
377
+ Ne promets jamais une parité qui n'existe pas — `npx nodefony inspect stores` dit où atterrit
378
+ chaque donnée.
379
+ - **Les violations de contrainte sont DÉJÀ traduites en HTTP — ne les rattrape pas.** Un doublon
380
+ sur une colonne unique ressort en **409**, une donnée qui viole le schéma Zod en **422**, chacun
381
+ avec son corps JSON : le rendu d'erreur lit le code du pilote (`23505`, `ER_DUP_ENTRY`,
382
+ `SQLITE_CONSTRAINT_UNIQUE`, `11000`) et le mappe, quel que soit le moteur. N'écris donc JAMAIS un
383
+ `throw … 409` pour un identifiant déjà pris. Le vérifier toi-même d'abord (« existe-t-il ? » puis
384
+ insertion) est plus lent ET **faux sous concurrence** : deux requêtes simultanées passent toutes
385
+ les deux le test avant que l'une n'écrive. La contrainte de la base est le seul arbitre exact.
386
+ - **Un fichier ne se sert pas à la main.** Trois façades, choisies sur l'usage :
387
+ `this.renderMediaStream(file)` implémente les **requêtes par plage** (`Range` → 206 +
388
+ `Content-Range`, 416 hors plage) — ce qu'exige un lecteur vidéo ou audio pour se déplacer ;
389
+ `this.streamFile(file)` envoie le fichier ENTIER en flux ; `this.renderFileDownload(file)` force
390
+ le téléchargement. Recomposer ça avec `createReadStream` et `response.write` compile, passe les
391
+ tests — et rend une réponse **incohérente** : un statut posé à la main n'atteint jamais la socket
392
+ (le pipeline écrit statut et en-têtes à SON tour), donc le client reçoit **200 avec un corps
393
+ partiel** et croit tenir le fichier complet. Mesuré au banc, pas supposé.
394
+ - **Un pod dont la base est en retard répond 503 sur `/readyz`** (jamais sur `/livez`) et reste
395
+ hors du répartiteur : c'est voulu, ce n'est pas une panne. `npx nodefony orm:migrate:status` dit
396
+ qui est en retard ; applique les migrations, les pods se mettent en service SEULS.
397
+
398
+ ## 9. Les pièges du front
399
+
400
+ Ne concerne qu'une application qui a un frontend. Sauf mention, **vaut pour les quatre moteurs**
401
+ (React, Vue, Angular, Svelte) : le framework ne t'en impose aucun.
402
+
403
+ ### Ce qui fuit, et ce qui s'injecte
404
+
405
+ - 🔴 **Toute donnée non maîtrisée se rend en nœud TEXTE.** Jamais `dangerouslySetInnerHTML`
406
+ (React), `v-html` (Vue), `[innerHTML]` (Angular), `{@html}` (Svelte). Un Markdown se rend sans
407
+ HTML brut.
408
+ - 🔴 **L'identité est un cookie `HttpOnly` que le navigateur joint seul.** Aucun jeton en
409
+ `localStorage` ou `sessionStorage`, aucun en-tête `Authorization` écrit à la main. Un `401`
410
+ est un ÉTAT (« pas connecté »), pas une erreur à journaliser.
411
+ - 🔴 **Le bundle front n'importe jamais un module serveur** — `@nodefony/http`,
412
+ `@nodefony/security`, une entité ORM, `nodefony.config`, `.env`. Un type serveur passe par
413
+ `import type` ou un miroir. Le test qui tranche : **si l'import tire un `node:*`, arrête-toi**
414
+ — ce bundle est public.
415
+ - **Rien de secret ne part côté client.** Toute clé `VITE_*` est lue par le navigateur ; un
416
+ `console.*` de données committé est une fuite. Le front AFFICHE ce qu'il reçoit — la rédaction
417
+ des secrets est un travail de serveur.
418
+
419
+ ### Charger des données sans casser l'écran
420
+
421
+ - **Un écran qui charge a quatre états EXCLUSIFS**, dans cet ordre de priorité : erreur (avec un
422
+ « réessayer »), chargement (un squelette qui épouse la page), vide (qui dit POURQUOI), données.
423
+ Un seul visible à la fois.
424
+ - **Tout chargement porte un jeton de génération** : une réponse arrivée après démontage, ou
425
+ après un changement de paramètre, est IGNORÉE — sinon un écran affiche les données d'un autre.
426
+ `loading` vaut `true` dès le montage, jamais après. Le framework ne fournit pas de hook de
427
+ ressource : c'est à écrire, dans les quatre moteurs.
428
+ - **Au vrai changement de compte** (l'identifiant passe d'une valeur à une AUTRE), force
429
+ `disconnect()` puis `connect()` sur la socket partagée et purge les caches propres à
430
+ l'utilisateur. Jamais au démarrage ni au rechargement : tu couperais les requêtes en vol.
431
+
432
+ ### Un écran qui se met à jour tout seul
433
+
434
+ - **Un widget temps réel est invisible tant qu'il ne se passe rien.** Format par paliers (jamais
435
+ une valeur qui saute de millisecondes à secondes), `font-variant-numeric: tabular-nums` sur
436
+ tout nombre qui change, aucune animation rejouée à chaque tick, `prefers-reduced-motion`
437
+ respecté, et un contrôle pause/fréquence dès que ça bouge seul plus de 5 s (WCAG 2.2.2).
438
+ **Le test** : fixe l'écran 30 secondes — rien ne doit bouger sans cause.
439
+ - **N'anime que `transform` et `opacity`** ; `contain: content` sur chaque widget vivant,
440
+ `content-visibility: auto` sur les longues listes.
441
+ - **Accessibilité, le minimum qui se vérifie à l'œil** : un seul `<h1>` par page, `aria-label`
442
+ sur tout bouton-icône, `aria-expanded` sur tout bascule, `aria-live` sur les zones qui changent
443
+ seules, `role="img"` + `aria-label` sur un graphe SVG, jamais une information portée par la
444
+ **couleur seule**, `rel="noopener noreferrer"` sur les liens externes.
445
+
446
+ - **Une route d'API répond du HTML** : un repli SPA générique avale les routes voisines — le
447
+ premier motif qui correspond gagne. Repli en préfixe LITTÉRAL ; `npx nodefony inspect routes
448
+ --json` montre l'ordre réel.
449
+ - **Des utilisateurs « déconnectés au hasard »** : le traitement global « 401 = session expirée »
450
+ frappe aussi les sondes d'authentification, où 401 est NORMAL — et détruit une session valide.
451
+ Exempte les sondes du traitement global.
452
+ - **Ta page répond 200 et son script ne s'exécute pas** : la politique de contenu exige un `nonce`
453
+ sur les scripts, et le navigateur refuse un `<script>` en ligne qui n'en porte pas (« Refused to
454
+ execute inline script »). Un `curl` ne le voit JAMAIS, il ne lit que le corps. Signe le script
455
+ (valeur `this.context?.cspNonce`) ou sors-le dans un fichier servi ; ne desserre PAS la politique.
456
+ - **En production, la modification front n'apparaît jamais** : hors développement il n'y a PAS de
457
+ rechargement à chaud, et le manifeste est lu AU BOOT. `npm run build`, puis **redémarre le
458
+ serveur**, puis rechargement forcé.
459
+
460
+ ### Le piège qui fait croire à un bug de code
461
+
462
+ - **Vite affirme qu'un export n'existe pas** (`does not provide an export named …`) alors qu'il
463
+ est bien dans le source — typiquement après un nouveau sous-chemin `nodefony/*`, une dépendance
464
+ ajoutée, ou un `git pull`. C'est son cache : `rm -rf node_modules/.vite`, puis relance. Ne
465
+ purge pas sans raison, ça coûte 5 à 20 s de ré-optimisation.
466
+
467
+ ## 10. Avant de dire « fait »
468
+
469
+ ```bash
470
+ npm run verify # types + style + tests + câblage
471
+ npm run test:e2e # boot réel + HTTP/WS — le gate LENT, hors `verify`
472
+ ```
473
+
474
+ Puis, en une phrase : **nomme ce que tu n'as PAS lancé.** Un vert ne couvre que le diff qui l'a
475
+ produit, et un banc sauté faute de son décor compte comme vert. Dire « e2e non lancé » coûte
476
+ cinq mots ; le taire coûte la confiance dans tout le reste.
477
+
478
+ Quatre règles de plus, chacune payée par une conclusion fausse qu'on a crue. Elles ne parlent
479
+ pas de ce framework en particulier — elles parlent de la façon dont une preuve se fabrique.
480
+
481
+ - 🔴 **Ta preuve porte sur l'artefact qu'on REÇOIT, pas sur ce que tu viens d'écrire.** Le
482
+ runtime charge `dist/`, une image embarque ce que le `Dockerfile` a copié, un paquet publié
483
+ contient ce que `files` laisse passer. Et avant de mesurer, vérifie que la transformation a
484
+ bien EU LIEU (date, empreinte) : dans une chaîne `a && b && c`, un maillon qui échoue laisse
485
+ mesurer l'ancienne version — et « prouver » qu'un correctif ne change rien.
486
+ - 🔴 **Un test que tu n'as jamais vu ROUGE ne prouve rien.** Écris-le, puis casse exprès ce
487
+ qu'il garde : retire le correctif, débranche le câblage. S'il reste vert, il ne mesure pas ce
488
+ que tu crois. Remets, et alors seulement crois-le. Un test écrit face au code déjà corrigé est
489
+ complaisant par construction.
490
+ - **Un décor SALE fabrique des verdicts faux** : un serveur resté ouvert sur le port, une base
491
+ jamais purgée, une variable d'environnement absente. Avant d'accuser ton code, qualifie le
492
+ rouge sur un décor NEUF — sinon tu corriges un problème qui n'existe pas, et tu laisses
493
+ intact celui qui existe.
494
+ - **Suspecte ton instrument avant de suspecter le code.** Une commande qui rend « 0 résultat »,
495
+ un compteur à zéro, un journal vide : demande-toi d'abord si l'outil regarde au bon endroit.
496
+ Une sortie tronquée, un filtre trop étroit, un chemin qui n'existe plus ne s'annoncent jamais
497
+ — ils rendent un silence qui ressemble à une réponse.