@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.
- package/dist/_virtual/{_@oxc-project_runtime@0.149.0 → _@oxc-project_runtime@0.150.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.149.0 → _@oxc-project_runtime@0.150.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.149.0 → _@oxc-project_runtime@0.150.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +2 -2
- package/dist/nodefony/controllers/DevkitController.js +2 -2
- package/dist/nodefony/controllers/McpController.js +3 -3
- package/dist/nodefony/service/DevkitService.js +2 -2
- package/docs/index.md +2 -2
- package/package.json +8 -8
- package/skills/nodefony-add-crud/SKILL.md +11 -2
- package/skills/nodefony-add-realtime-channel/SKILL.md +9 -0
- package/skills/nodefony-add-service/SKILL.md +9 -0
- package/skills/nodefony-browser/SKILL.md +31 -23
- package/skills/nodefony-dev/SKILL.md +497 -0
- package/skills/nodefony-dev/scripts/docs.mjs +544 -0
- package/skills/nodefony-devops/SKILL.md +198 -0
- package/skills/nodefony-devops/references/compose.md +125 -0
- package/skills/nodefony-devops/references/frontal.md +119 -0
- package/skills/nodefony-devops/references/image.md +136 -0
- package/skills/nodefony-devops/references/kubernetes.md +189 -0
- package/skills/nodefony-devops/references/podman.md +103 -0
- package/skills/nodefony-devops/references/secrets.md +108 -0
- package/skills/nodefony-devops/references/variables.md +174 -0
- package/skills/nodefony-migrate-schema/SKILL.md +50 -17
- 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.
|