@nodefony/devkit 10.0.0-beta.2 → 10.0.0-beta.3

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 (36) hide show
  1. package/README.md +5 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateParam.js +1 -1
  5. package/dist/index.js +2 -2
  6. package/dist/nodefony/controllers/DevkitController.js +2 -2
  7. package/dist/nodefony/controllers/McpController.js +71 -6
  8. package/dist/nodefony/service/DevkitService.js +2 -2
  9. package/dist/nodefony/src/card.js +1 -2
  10. package/dist/types/nodefony/controllers/McpController.d.ts +12 -1
  11. package/docs/index.md +62 -14
  12. package/package.json +9 -9
  13. package/skills/nodefony-add-realtime-channel/SKILL.md +66 -9
  14. package/skills/nodefony-add-service/SKILL.md +11 -0
  15. package/skills/nodefony-authentication/SKILL.md +234 -0
  16. package/skills/nodefony-browser/SKILL.md +45 -21
  17. package/skills/nodefony-browser/references/sondes.md +13 -0
  18. package/skills/nodefony-browser/scripts/audit.mjs +59 -10
  19. package/skills/nodefony-browser/scripts/inspect.mjs +122 -4
  20. package/skills/nodefony-browser/scripts/lib/browser.mjs +112 -6
  21. package/skills/nodefony-browser/scripts/lib/probes.mjs +166 -7
  22. package/skills/nodefony-dev/SKILL.md +47 -81
  23. package/skills/nodefony-dev/scripts/docs.mjs +21 -5
  24. package/skills/nodefony-devops/SKILL.md +16 -13
  25. package/skills/nodefony-devops/references/compose.md +8 -0
  26. package/skills/nodefony-devops/references/frontal.md +5 -1
  27. package/skills/nodefony-devops/references/secrets.md +18 -4
  28. package/skills/nodefony-devops/references/variables.md +52 -19
  29. package/skills/nodefony-migrate-schema/SKILL.md +5 -2
  30. package/skills/nodefony-migrate-schema/references/verdicts.md +20 -19
  31. package/skills/nodefony-protect-route/SKILL.md +118 -17
  32. package/skills/nodefony-security-review/SKILL.md +127 -0
  33. package/skills/nodefony-security-review/scripts/review-diff.mjs +1090 -0
  34. package/skills/nodefony-troubleshoot/SKILL.md +151 -0
  35. package/skills/nodefony-troubleshoot/scripts/log-errors.mjs +465 -0
  36. package/skills/nodefony-upgrade/SKILL.md +130 -0
@@ -14,7 +14,7 @@ description: >
14
14
  est-il fini", "je ne trouve rien sur ce sujet", "ce n'est pas documenté", "je touche au
15
15
  frontend", "mon composant charge des données", "mon écran n'affiche rien".
16
16
  metadata:
17
- version: 1.2.0
17
+ version: 1.3.0
18
18
  ---
19
19
 
20
20
  # nodefony-dev — développer dans cette application sans rien inventer
@@ -45,15 +45,19 @@ de son geste, et ceux-là ne se devinent pas — ils se paient.
45
45
  | -------------------------------------------------------------------- | ------------------------------- |
46
46
  | Une ressource : table, validation, service, REST et WebSocket | `nodefony-add-crud` |
47
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` |
48
+ | Un flux temps réel (WebSocket ou SSE), un canal, un abonnement | `nodefony-add-realtime-channel` |
49
+ | Se connecter : /login, second facteur, retour après connexion, jeton | `nodefony-authentication` |
50
+ | Réserver une route ou une page à des rôles, ouvrir à un partenaire | `nodefony-protect-route` |
50
51
  | Changer une entité déjà en base, ou déployer un schéma changé | `nodefony-migrate-schema` |
51
52
  | Conclure quoi que ce soit d'un ÉCRAN — affichage, contraste, console | `nodefony-browser` |
53
+ | Quelque chose qui marchait ne marche plus : démarrage, 404, journal | `nodefony-troubleshoot` |
54
+ | Relire la sécurité de ton diff AVANT de commiter | `nodefony-security-review` |
55
+ | Monter Nodefony de version, ou une montée qui a laissé des rouges | `nodefony-upgrade` |
52
56
 
53
57
  Aucune ligne ne correspond ? Alors seulement, continue ici. Et si ta tâche est EXACTEMENT celle
54
58
  d'un spécialiste, va droit à lui : il n'y a pas à passer par cette page d'abord.
55
59
 
56
- `ls .agents/skills/` liste ceux que TON projet a reçus ; `npx nodefony ai:sync` les remet à jour
60
+ `ls .agents/skills/` liste ceux que TON projet a reçus ; `npx nodefony upgrade` les remet à jour
57
61
  après une montée de version. Ce sont des **pointeurs** : le contenu vit dans `node_modules` et
58
62
  suit la version installée — les éditer ne servirait à rien.
59
63
 
@@ -152,7 +156,8 @@ npx nodefony symbols --module @nodefony/http # toute la surface exportée d'un
152
156
  ```
153
157
 
154
158
  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
159
+ dépend ni d'un serveur, ni d'un build, ni de ta connexion. Celui de TON code (`.ai/symbols.json`),
160
+ `npm run dev` le tient à jour — ou `npx nodefony symbols --generate` ; les deux sont lus ensemble. Va y chercher un symbole AVANT d'ouvrir
156
161
  un `.d.ts` — et avant, surtout, d'inventer une signature.
157
162
 
158
163
  **Si la commande te résiste, répare l'APPEL — ne te rabats pas sur les sources.** C'est le réflexe
@@ -171,25 +176,19 @@ ENSUITE. Tout le reste (déclarer un outil métier, le réserver à des scopes,
171
176
  ### L'environnement : ne devine JAMAIS, demande
172
177
 
173
178
  ```bash
174
- npx nodefony env # cascade des .env, valeur EFFECTIVE de chaque variable, sa PROVENANCE
179
+ npx nodefony env # valeur EFFECTIVE de chaque variable, sa PROVENANCE
175
180
  ```
176
181
 
177
182
  **Encadre toute modification de configuration par cette commande** : une fois AVANT, pour savoir ce
178
183
  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
184
+ Lire `.env` toi-même donne un contenu ; ce que l'application voit dépend aussi du shell et de
185
+ `env.ts` — tu ne peux que le supposer, et une supposition fausse ne se voit qu'en production. La commande ne boote rien, donc
181
186
  elle répond aussi quand l'application ne démarre plus.
182
187
 
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.
188
+ **Deux fichiers** : `.env` = valeurs du POSTE (secrets de dev compris), **jamais commité** ;
189
+ `.env.example` = notice commitée, **jamais chargée**. `process.env` > `.env`, rien d'autre. La
190
+ production n'a **aucun** fichier (orchestrateur ; réglages non secrets dans `nodefony.config.ts`).
191
+ Un module n'a pas de `.env`. N'écris JAMAIS `.env.local` ni `.env.<mode>` : refusés au démarrage (78).
193
192
 
194
193
  | Forme | Ce que c'est | Où c'est déclaré |
195
194
  | ------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------ |
@@ -261,6 +260,13 @@ une table à part — et ce nom est ce que `stop` accepte. Donc « aucune instan
261
260
  REFUSÉE, avec un code de sortie non nul et rien d'arrêté — **lis ce code**, un refus ressemble
262
261
  sinon à un succès.
263
262
 
263
+ **Le plein écran n'est pas pour toi — et il est le DÉFAUT sous macOS et Linux.** Il fige la barre
264
+ d'état en bas, capte le clavier et la souris (molette, PgUp/PgDn, Ctrl+C devient une touche) :
265
+ c'est un écran pour un humain. Il s'active dès que le terminal répond à une sonde, et un
266
+ pseudo-terminal (tmux, un outil qui t'en fournit un) y répond. Nodefony ne devine jamais qu'un
267
+ agent est aux commandes : si tu lances `development` toi-même, ajoute **toujours** `--no-ui`
268
+ (ou pose `NF_DEV_UI=0`), ou `--output plain` pour une sortie sans séquences d'écran.
269
+
264
270
  **Pour faire tourner une suite contre un serveur, prends `--no-watch`.** Le mode développement
265
271
  relance le serveur dès qu'un fichier bouge : pendant un run, le redémarrage coupe les connexions
266
272
  sous les tests, et le rouge qui en sort accuse le code alors que le fautif est le décor.
@@ -283,8 +289,9 @@ routes auth` ne rend que les routes dont le chemin, le nom, le contrôleur, l'ac
283
289
  4. **Édite le moins possible**, et regroupe : toutes les modifications serveur d'une même
284
290
  fonctionnalité, PUIS un seul cycle de reconstruction. Le frontend passe en HMR, zéro
285
291
  redémarrage.
286
- 5. **Prouve.** `npm run verify` — une seule commande : types, style, tests, câblage, dans cet
287
- ordre. Elle s'arrête au premier rouge, et **ce rouge est ta tâche suivante**.
292
+ 5. **Prouve.** `npm run verify` — une seule commande : types, style, mise en forme,
293
+ `.env.example`, tests, build, câblage, dans cet ordre. Elle s'arrête au premier rouge, et
294
+ **ce rouge est ta tâche suivante**.
288
295
 
289
296
  **Le point d'arrêt qu'on rate** : `npm test` seul ne prouve pas que ça compile — vitest
290
297
  n'inspecte aucun type. Une application peut être verte et ne pas compiler.
@@ -304,50 +311,19 @@ ici — ils changent tous les trimestres ; raisonne en poids.
304
311
 
305
312
  ## 7. Les pièges du serveur
306
313
 
307
- Chacun a déjà coûté au moins une heure à quelqu'un. Les quatre premiers sont les plus fréquents.
308
-
309
- - **Ta route répond 404 alors qu'elle existe dans les sources.** Le runtime charge `dist/`, pas
310
- le source : `npm run build`. C'est la cause n°1.
311
- - **Une classe que rien ne déclare compile, passe ses tests, et casse au démarrage suivant** —
312
- entité hors `@entities([…])`, controller hors `@controllers([…])`. Table jamais créée, route
313
- en 404. C'est le mode d'échec de la COPIE : on recopie le voisin au lieu d'appeler le
314
- générateur, qui, lui, déclare. `npx nodefony doctor` la NOMME.
315
- - **Une clé de configuration mal orthographiée est retirée en silence.** `satisfies` sur le
316
- fragment n'est pas décoratif : sans lui, la faute compile, puis la validation écarte la clé et
317
- le module démarre sur son défaut. Personne ne le voit.
314
+ Ceux qu'on pose en ÉCRIVANT. Ceux qu'on rencontre quand quelque chose qui marchait ne marche plus
315
+ — démarrage qui échoue, route en 404, refus inattendu, journal en erreur, tests rouges d'un coup —
316
+ ont leur skill, qui part du symptôme : **`nodefony-troubleshoot`**.
317
+
318
+ - **Une clé de configuration mal orthographiée ARRÊTE le démarrage** (code 78), en la nommant.
319
+ `satisfies` sur un fragment extrait n'est pas décoratif pour autant : il te la signale à la
320
+ compilation, au lieu du prochain démarrage.
318
321
  - **La config d'un module est FIGÉE après le démarrage** : l'écrire lève `TypeError`. Une variante
319
322
  par requête = `overlayConfig()` (clés de la liste blanche du module), lue par `useConfig()`.
320
323
  - **Tu lis une liste sans la BORNER.** Un `find` sans limite matérialise la table entière —
321
324
  indolore sur les quelques lignes du poste de développement, fatal sur les dizaines de milliers
322
325
  de la production. Le service d'une entité hérite `findPage({ limit: 25 })`.
323
326
 
324
- - **Des dizaines de tests d'intégration rouges d'un coup** (`ECONNREFUSED`) : ils FRAPPENT un
325
- serveur, ils ne le lancent pas — il est éteint. `npx nodefony status` d'abord ; en e2e, laisse
326
- la commande gérer le cycle.
327
- - **Ta route NEUVE répond 404 et le `dist/` est à jour** : elle n'est pas montée où tu crois. Le
328
- chemin réel est le PRÉFIXE de son controller suivi du `path` de la route — une action
329
- `path: "/widget"` dans un controller `@controller("/api")` répond sur `/api/widget`.
330
- `npx nodefony inspect routes --json` donne le chemin MONTÉ.
331
- - **TOUT répond 404, même les routes du gabarit** : un AUTRE serveur tient les ports — ou LE TIEN
332
- a glissé, le port voulu étant pris. `npx nodefony status` montre les ports RÉELS, pas ceux que
333
- tu as configurés, et NOMME le projet voisin.
334
- - 🔴 **`nodefony <commande>` échoue là où `npm run dev` réussit, sur la MÊME application** : DEUX
335
- paquets `nodefony` tournent dans le processus — un binaire lié ailleurs (installation globale,
336
- `npm link`) exécute un noyau pendant que l'application importe le sien. Chaque copie a ses
337
- propres classes, son contexte de requête et ses registres d'injection : un module construit à la
338
- frontière perd son container SANS la moindre erreur — il figure dans la liste des modules et ne
339
- fait rien. ⚠️ Les deux copies peuvent être la MÊME version ; deux fichiers distincts suffisent,
340
- il n'y a aucune incohérence à repérer. `npm ls nodefony` liste les copies (« deduped » partout =
341
- une seule) ; `NF_CLI_DEBUG=1 nodefony --version` dit quel CLI s'exécute.
342
- - **`localhost` et `127.0.0.1` te jouent des tours** : ce sont deux ORIGINES distinctes — cookies,
343
- cache et passkeys ne les partagent pas. Une seule origine en développement, partout, URL ouverte
344
- comme callbacks.
345
-
346
- - **Les routes authentifiées plafonnent quand le reste tient la charge** : le stockage de session
347
- par défaut est SYNCHRONE — chaque reprise bloque la boucle d'événements. Compare une route
348
- anonyme et une route authentifiée AVANT d'accuser TLS ou le pare-feu ; passe le stockage sur
349
- redis pour la charge.
350
-
351
327
  ### Ce qui tue le processus, pas la requête
352
328
 
353
329
  - 🔴 **Une promesse lancée sans `await` porte TOUJOURS un `.catch()`.** Audit, courriel,
@@ -361,24 +337,6 @@ Chacun a déjà coûté au moins une heure à quelqu'un. Les quatre premiers son
361
337
  - **Un `PATCH` au corps vide se refuse en `400`.** Le laisser passer produit un `updateOne({})`,
362
338
  qui finit en `500 « No values to set »` — une erreur serveur pour une faute de client.
363
339
 
364
- ### Quand ça ne va pas, et que le message ne suffit pas
365
-
366
- - **Un test vert seul et rouge en suite accuse une RESSOURCE PARTAGÉE**, pas ton code : base ou
367
- index réutilisé, port, store jamais purgé, assertion `count === N` qui suppose une table
368
- vierge. Lance deux fichiers ENSEMBLE pour isoler la paire, puis cloisonne. **Ne sérialise
369
- jamais la suite** pour faire passer le rouge : tu masques la cause et tu paies la lenteur à
370
- chaque exécution.
371
- - **Une suite lancée contre un serveur en `production` reçoit `404` partout** : les modules
372
- `policy:"dev"` n'y sont pas. `NF_WITH_DEV_MODULES=1` déroge pour 30 minutes
373
- (`NF_WITH_DEV_MODULES_TTL_MIN`, 4 h au plus), et le `CRITIC « arrêt automatique … dérogation »`
374
- du journal est cette garde qui se referme — pas une panne.
375
- - **Ton application a démarré AMPUTÉE et tout a l'air sain** — base injoignable, module écarté
376
- par sa politique. Seul `npx nodefony doctor` le dit, en relisant `var/last-boot.json`.
377
- ⚠️ Ce bilan a un ÂGE : il décrit le **dernier démarrage**, et une commande console (`inspect`,
378
- une commande de module) ne l'écrase pas. C'est voulu — lis la date avant d'en conclure.
379
- - **L'application ne démarre plus et le superviseur avale la sortie** :
380
- `NF_DEV_CHILD=1 npx nodefony development` lance l'enfant seul et montre le crash brut.
381
-
382
340
  ## 8. Données et fichiers — les façades qu'on ne recompose pas
383
341
 
384
342
  - **Un cache ne remplace pas une base : ils se COMPLÈTENT.** Chaque adaptateur déclare les
@@ -454,11 +412,16 @@ Ne concerne qu'une application qui a un frontend. Sauf mention, **vaut pour les
454
412
  **couleur seule**, `rel="noopener noreferrer"` sur les liens externes.
455
413
 
456
414
  - **Une route d'API répond du HTML** : un repli SPA générique avale les routes voisines — le
457
- premier motif qui correspond gagne. Repli en préfixe LITTÉRAL ; `npx nodefony inspect routes
458
- --json` montre l'ordre réel.
415
+ premier motif qui correspond gagne. Déclare la route de la page monopage avec
416
+ `fallback: true` : elle ne sert alors que ce qu'aucune autre route ne sert, quel que soit
417
+ l'ordre. `npx nodefony inspect routes --json` montre l'ordre réel.
459
418
  - **Des utilisateurs « déconnectés au hasard »** : le traitement global « 401 = session expirée »
460
- frappe aussi les sondes d'authentification, où 401 est NORMAL — et détruit une session valide.
461
- Exempte les sondes du traitement global.
419
+ frappe aussi la connexion refusée (un mauvais mot de passe rend 401) — et détruit une session
420
+ valide. Exempte les routes d'authentification du traitement global. `me` ne renvoie jamais
421
+ 401 : il répond toujours 200, `{ user: null }` sans session.
422
+ - **La connexion ne s'écrit pas en `fetch`** : `/login` est servie ; la liaison du front
423
+ (`useNodefonyLogin`…) porte `me()` et `logout()` — suis l'adresse que rend `logout()`, sinon
424
+ la session Keycloak reste ouverte. Détail : skill `nodefony-authentication`.
462
425
  - **Ta page répond 200 et son script ne s'exécute pas** : la politique de contenu exige un `nonce`
463
426
  sur les scripts, et le navigateur refuse un `<script>` en ligne qui n'en porte pas (« Refused to
464
427
  execute inline script »). Un `curl` ne le voit JAMAIS, il ne lit que le corps. Signe le script
@@ -477,10 +440,13 @@ Ne concerne qu'une application qui a un frontend. Sauf mention, **vaut pour les
477
440
  ## 10. Avant de dire « fait »
478
441
 
479
442
  ```bash
480
- npm run verify # types + style + tests + câblage
443
+ npm run verify # types, style, mise en forme, .env.example, tests, build, câblage
481
444
  npm run test:e2e # boot réel + HTTP/WS — le gate LENT, hors `verify`
482
445
  ```
483
446
 
447
+ Ton diff ajoute une route, une requête ou touche la sécurité ? Relis-le AVANT de commiter :
448
+ skill `nodefony-security-review` — un script y nomme, ligne par ligne, ce qu'un œil laisse passer.
449
+
484
450
  Puis, en une phrase : **nomme ce que tu n'as PAS lancé.** Un vert ne couvre que le diff qui l'a
485
451
  produit, et un banc sauté faute de son décor compte comme vert. Dire « e2e non lancé » coûte
486
452
  cinq mots ; le taire coûte la confiance dans tout le reste.
@@ -21,10 +21,23 @@
21
21
  * `@output` les pages classées, avec chemin, ligne et extrait — ou un refus qui DIT pourquoi
22
22
  */
23
23
 
24
- import { readFileSync, readdirSync, statSync } from "node:fs";
24
+ import { readFileSync, readdirSync, statSync, realpathSync } from "node:fs";
25
25
  import path from "node:path";
26
26
  import process from "node:process";
27
- import { pathToFileURL } from "node:url";
27
+ import { fileURLToPath } from "node:url";
28
+ import { findProjectRoot } from "nodefony/project-root";
29
+
30
+ /**
31
+ * Racine de l'APPLICATION vue depuis `cwd` — le dossier qui porte
32
+ * `nodefony.config.ts` —, ou `cwd` lui-même hors application. Un script lancé
33
+ * depuis `frontend/` ou `modules/blog/` travaille ainsi sur l'app entière.
34
+ * `nodefony/project-root` est le sous-chemin LÉGER du cœur (`node:` seul) :
35
+ * importer `nodefony` entier coûtait jusqu'à plusieurs secondes à froid.
36
+ *
37
+ * @param {string} cwd - le dossier de lancement.
38
+ * @returns {string} la racine retenue.
39
+ */
40
+ const appRoot = (cwd) => findProjectRoot(cwd) ?? cwd;
28
41
 
29
42
  /** Nombre de pages rendues par défaut — au-delà, personne ne lit. */
30
43
  const DEFAULT_LIMIT = 6;
@@ -55,14 +68,14 @@ const FLAGS = new Set([
55
68
  * @returns les options lues.
56
69
  * @throws Error quand un drapeau est inconnu ou qu'une valeur manque.
57
70
  */
58
- export function parseArgs(argv) {
71
+ export function parseArgs(argv, cwd = process.cwd()) {
59
72
  const opts = {
60
73
  terms: [],
61
74
  list: false,
62
75
  open: /** @type {string | null} */ (null),
63
76
  json: false,
64
77
  limit: DEFAULT_LIMIT,
65
- root: process.cwd(),
78
+ root: appRoot(cwd),
66
79
  help: false,
67
80
  };
68
81
  for (let i = 0; i < argv.length; i += 1) {
@@ -538,7 +551,10 @@ export function main() {
538
551
  // confondrait deux scripts homonymes de deux skills.
539
552
  if (
540
553
  process.argv[1] !== undefined &&
541
- pathToFileURL(process.argv[1]).href === import.meta.url
554
+ // Chemins RÉELS des deux côtés : Node résout `import.meta.url` en suivant
555
+ // les liens, pas `argv[1]` — lancé par `/var/…` (macOS) ou un dossier lié,
556
+ // la comparaison brute échouait et le script ne faisait RIEN.
557
+ realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
542
558
  ) {
543
559
  process.exitCode = main();
544
560
  }
@@ -75,7 +75,8 @@ Ce que l'application ne peut pas deviner. Chacun de ces oublis produit une dégr
75
75
 
76
76
  - **`NF__HTTP__TRUSTPROXY`** dès qu'un frontal termine le TLS. Sans lui, l'application constate un
77
77
  schéma `http` et **un cookie `__Host-` n'est pas émis** : les sessions ne tiennent plus, sans le
78
- moindre message. Détail et preuve → [`references/frontal.md`](references/frontal.md).
78
+ moindre message — et toute connexion par mot de passe répond `403`, le secret étant reçu
79
+ « en clair ». Détail et preuve → [`references/frontal.md`](references/frontal.md).
79
80
  - **Deux volumes inscriptibles**, sur `/app/tmp` et `/app/var`, si tu scelles la racine.
80
81
  - **Une fenêtre d'arrêt ≥ 30 s**, strictement supérieure au drain de 15 s.
81
82
  - **Pas de limite processeur.** Une limite mémoire tue proprement et l'exemplaire redémarre ; une
@@ -106,18 +107,20 @@ Chaque ligne a la commande qui la **prouve**. Une case cochée sans commande est
106
107
 
107
108
  Le tableau qu'on lit en urgence.
108
109
 
109
- | Symptôme | Cause | Correction |
110
- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
111
- | L'exemplaire redémarre en boucle, journaux muets | La sonde de vivacité tombe avant la fin du démarrage | Sonde de démarrage sur `/readyz`, dimensionnée sur le **pire** cas |
112
- | Le conteneur sort en **137** à l'arrêt | Il a été tué : la fenêtre d'arrêt est ≤ au drain | Fenêtre ≥ 30 s (Podman : `--stop-timeout 20`, son défaut est **10 s**) |
113
- | Des `5xx` **pendant** chaque déploiement | Le répartiteur envoie encore du trafic quand le drain a commencé | Un délai avant drain, et un court arrêt avant terminaison |
114
- | Les sessions ne tiennent pas derrière le frontal | Schéma constaté `http` → le cookie `__Host-` n'est pas émis | `NF__HTTP__TRUSTPROXY` |
115
- | Les journaux d'audit montrent tous la **même adresse** | L'adresse du client n'est pas propagée, ou le frontal n'est pas de confiance | `trustProxy` ; en Podman sans privilèges, **c'est irréparable** (§7) |
116
- | `EACCES` au premier démarrage sur un volume | Le volume est né `root:root` : monté **par-dessus** un dossier dont l'image avait fixé le porteur | Volume nommé (il hérite du porteur) ou groupe de système de fichiers |
117
- | La base refuse des connexions dès qu'on monte en exemplaires | Une base administrée les compte ; le pool n'est pas borné | Réduire les exemplaires — le plafond n'est pas encore réglable (§6) |
118
- | La sonde répond `429` | Ce n'est **pas** la sonde : c'est la limitation de débit — donc l'appel n'a pas pris le raccourci | Interroger `/livez` et `/readyz` nus, sans en-tête ni préfixe ajouté |
119
- | Un secret retiré du `Dockerfile` reste dans l'image | Une couche antérieure le contient encore ; l'effacer plus loin ne l'enlève pas | `npx nodefony image:check`, et reconstruire sans jamais l'avoir copié |
120
- | La variable posée « ne fait rien » | Le nom est faux d'un caractère — une variable inconnue est **ignorée**, pas refusée | `npx nodefony doctor` → [`references/variables.md`](references/variables.md) |
110
+ | Symptôme | Cause | Correction |
111
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | L'exemplaire redémarre en boucle, journaux muets | La sonde de vivacité tombe avant la fin du démarrage | Sonde de démarrage sur `/readyz`, dimensionnée sur le **pire** cas |
113
+ | Le conteneur sort en **137** à l'arrêt | Il a été tué : la fenêtre d'arrêt est ≤ au drain | Fenêtre ≥ 30 s (Podman : `--stop-timeout 20`, son défaut est **10 s**) |
114
+ | Des `5xx` **pendant** chaque déploiement | Le répartiteur envoie encore du trafic quand le drain a commencé | Un délai avant drain, et un court arrêt avant terminaison |
115
+ | Les sessions ne tiennent pas derrière le frontal | Schéma constaté `http` → le cookie `__Host-` n'est pas émis | `NF__HTTP__TRUSTPROXY` |
116
+ | Toutes les connexions répondent `403` « doit passer par HTTPS » | Même cause : le mot de passe arrive sur une requête que l'application croit en clair | `NF__HTTP__TRUSTPROXY` — jamais `allowInsecureCredentials` |
117
+ | Sortie **78** au démarrage, une clé ou une variable nommée | Faute de configuration : clé inconnue (ou retirée par une mise à jour), valeur invalide, secret requis absent (`NF_CSRF_SECRET`, `NF_JWT_KEYSET`) | Lire la ligne qui la nomme ; `npx nodefony doctor --env production` annonce AVANT les variables requises (pas `NF_JWT_KEYSET`, exigée seulement si l'app émet des jetons) |
118
+ | Les journaux d'audit montrent tous la **même adresse** | L'adresse du client n'est pas propagée, ou le frontal n'est pas de confiance | `trustProxy` ; en Podman sans privilèges, **c'est irréparable** (§7) |
119
+ | `EACCES` au premier démarrage sur un volume | Le volume est né `root:root` : monté **par-dessus** un dossier dont l'image avait fixé le porteur | Volume nommé (il hérite du porteur) ou groupe de système de fichiers |
120
+ | La base refuse des connexions dès qu'on monte en exemplaires | Une base administrée les compte ; le pool n'est pas borné | Réduire les exemplaires — le plafond n'est pas encore réglable (§6) |
121
+ | La sonde répond `429` | Ce n'est **pas** la sonde : c'est la limitation de débit — donc l'appel n'a pas pris le raccourci | Interroger `/livez` et `/readyz` nus, sans en-tête ni préfixe ajouté |
122
+ | Un secret retiré du `Dockerfile` reste dans l'image | Une couche antérieure le contient encore ; l'effacer plus loin ne l'enlève pas | `npx nodefony image:check`, et reconstruire sans jamais l'avoir copié |
123
+ | La variable posée « ne fait rien » | Le nom est faux d'un caractère — une variable inconnue est **ignorée**, pas refusée | `npx nodefony doctor` → [`references/variables.md`](references/variables.md) |
121
124
 
122
125
  ## 6. Kubernetes — ce qui est rendu, et ce qui te reste
123
126
 
@@ -16,6 +16,7 @@ docker compose --profile edge up -d # la TOPOLOGIE DE PRODUCTION : front
16
16
  docker compose --profile tools up -d # les explorateurs de données
17
17
  docker compose --profile loki up -d # journalisation centralisée + tableaux de bord
18
18
  docker compose --profile browser up -d # un navigateur piloté
19
+ docker compose --profile keycloak up -d keycloak # un fournisseur d'identité OpenID Connect, de DÉVELOPPEMENT
19
20
  ```
20
21
 
21
22
  > Le profil `app` et le profil `edge` sont **exclusifs par nature** : le premier publie un port sur
@@ -35,6 +36,13 @@ docker compose --profile browser up -d # un navigateur piloté
35
36
  | `redisinsight` | `tools` | explorateur du cache | `5540` |
36
37
  | `loki` / `grafana` | `loki` | journaux et tableaux de bord | `3100`, `3000` |
37
38
  | `browser` | `browser` | navigateur piloté (image **épinglée par empreinte**) | `3001` |
39
+ | `keycloak` | `keycloak` | `quay.io/keycloak/keycloak` (préréglage `complete`) | `8444` (8443 est au frontal) |
40
+
41
+ Le profil `keycloak` est un décor de **développement** (base embarquée, secrets publics) : realm
42
+ et thème viennent de `docker/keycloak/`, posés par le paquet `@nodefony/security` et tenus à jour
43
+ par `npx nodefony scaffold:sync --update`. Il exige `npx nodefony http:certificates` une fois
44
+ (Keycloak sert en https), et sous Linux natif `export KEYCLOAK_UID=$(id -u)` avant le `up`. Le
45
+ mode d'emploi est en commentaire au-dessus du service, dans `compose.yaml`.
38
46
 
39
47
  **Les ports sont publiés sur la boucle locale**, pas sur `0.0.0.0`. Une base de développement
40
48
  accessible depuis le réseau est une base de développement qui finit indexée.
@@ -34,7 +34,11 @@ Conséquence si tu ne le poses pas : le schéma constaté est `http`, donc :
34
34
  sécurisée. Les sessions ne tiennent pas, **sans aucun message** ;
35
35
  - l'adresse cliente enregistrée est celle du frontal, donc l'audit et toute limitation par adresse
36
36
  portent sur une seule IP ;
37
- - les redirections peuvent pointer sur `http://`.
37
+ - les redirections peuvent pointer sur `http://` ;
38
+ - **toute connexion par mot de passe répond `403`** (« doit passer par HTTPS ») : en production,
39
+ un secret reçu sur une requête que l'application croit en clair est refusé avant toute
40
+ vérification. `security.allowInsecureCredentials` n'est PAS la correction — elle accepterait
41
+ vraiment le clair ; la correction est cette variable.
38
42
 
39
43
  ```yaml
40
44
  NF__HTTP__TRUSTPROXY: uniquelocal # le frontal est sur un réseau privé
@@ -24,7 +24,7 @@ Il est rendu avec l'application, et il exclut quatre familles. Ne les retire pas
24
24
  | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
25
25
  | `**/node_modules`, `**/dist` | Reconstruits **dans** l'image. Entrés depuis ta machine, ils masqueraient la construction et l'image partirait avec le code de la veille. |
26
26
  | `var`, `logs`, `**/*.log`, `**/*.jsonl` | Écritures propres à une machine, sans valeur dans une image |
27
- | `*.local`, `**/*.local` | La convention des fichiers de secrets locaux — jamais commités, et pas davantage dans une image |
27
+ | `.env`, `.env.*` (sauf `.env.example`) | Les valeurs du poste, secrets de dev compris — jamais commitées, et pas davantage dans une image |
28
28
  | **La matière cryptographique** | Clés privées, certificats |
29
29
 
30
30
  > ⚠️ **Un motif de `.dockerignore` n'obéit pas aux règles du `.gitignore`.** Sans `**/`, il est
@@ -47,15 +47,29 @@ qu'aucun certificat auto-signé de ta machine ne descende en production.
47
47
  | `NF_CSRF_SECRET` | Les jetons anti-CSRF | **oui** — sinon un jeton émis par un exemplaire est refusé par l'autre |
48
48
  | `NF_TOTP_KEY` | Le chiffrement au repos des secrets de double authentification | **oui** — sinon les seconds facteurs deviennent illisibles |
49
49
  | `NF_WEBHOOK_KEY` | Le chiffrement des signatures de webhooks | **oui** |
50
+ | `NF_JWT_KEYSET` | La clé qui signe les jetons (privée) | **oui** — sinon chaque exemplaire refuse les jetons des autres |
50
51
  | `NF_ADMIN_PASSWORD` | Le compte d'administration | — |
51
52
 
52
53
  > 🔴 **Le partage entre exemplaires n'est pas un détail de confort.** Un secret différent par
53
54
  > exemplaire produit des échecs **intermittents** — une requête sur deux, selon le répartiteur.
54
55
  > C'est le symptôme le plus coûteux à diagnostiquer, parce qu'il ressemble à un problème réseau.
55
56
 
56
- `NF_ADMIN_PASSWORD` a un défaut en développement. **En production il est obligatoire** : sans lui,
57
- aucun compte d'administration n'est créé et le démarrage échoue — volontairement, plutôt que de
58
- créer un compte avec un mot de passe connu de tout le monde.
57
+ **Lesquels arrêtent le démarrage en production** : `NF_CSRF_SECRET` toujours, `NF_JWT_KEYSET`
58
+ dès que l'application émet des jetons. Sans `NF_TOTP_KEY` ou `NF_WEBHOOK_KEY`, l'application
59
+ démarre mais la brique se désactive (double authentification, webhooks), avec un journal CRITIC.
60
+
61
+ ```bash
62
+ npx nodefony security:secrets --env # les secrets, en lignes CLÉ=valeur (--env-file, gestionnaire)
63
+ npx nodefony security:secrets --jwt-keyset # la valeur de NF_JWT_KEYSET — une ligne JSON, clé PRIVÉE
64
+ ```
65
+
66
+ Une valeur par ENVIRONNEMENT (staging ≠ production), générée une fois, rangée dans le
67
+ gestionnaire de secrets ; ne laisse traîner aucun fichier qui la contient.
68
+
69
+ `NF_ADMIN_PASSWORD` a un défaut en développement, aucun en production : sans lui, le premier
70
+ démarrage ne crée **aucun** compte d'administration — plutôt qu'un compte au mot de passe connu de
71
+ tout le monde. Il ne sert qu'à ce premier démarrage ; `npx nodefony security:user:add <id> --admin`
72
+ fait le même geste à la main.
59
73
 
60
74
  ## 5. Les monter, pas les poser en clair
61
75
 
@@ -5,7 +5,7 @@
5
5
  ## 1. La commande qui répond, et à laquelle rien ne ment
6
6
 
7
7
  ```bash
8
- npx nodefony env # la cascade des .env, la valeur EFFECTIVE de chaque variable, et sa PROVENANCE
8
+ npx nodefony env # la valeur EFFECTIVE de chaque variable, et sa PROVENANCE (.env ou process)
9
9
  npx nodefony env --json # le même rapport, pour un script
10
10
  ```
11
11
 
@@ -19,8 +19,11 @@ npx nodefony inspect schema <module> # les clés qui EXISTENT, leur type,
19
19
  npx nodefony doctor --env production # ce qui manquerait là-bas, depuis ici
20
20
  ```
21
21
 
22
- > 🔴 **`inspect schema` avant d'écrire une clé.** Une clé inconnue n'est pas refusée : elle est
23
- > **retirée en silence** à la validation. Tu poses le réglage, rien ne change, rien ne le dit.
22
+ > 🔴 **`inspect schema` avant d'écrire une clé.** Une clé inconnue **arrête le démarrage**
23
+ > (code 78) en la nommant — y compris une clé qu'une version du framework a retirée, que tu
24
+ > découvres alors au premier déploiement après un `npm update`. Ce qui reste muet, c'est une
25
+ > **variable** mal orthographiée (`NF_PROT` au lieu de `NF_PORT`) : inconnue, donc ignorée, et
26
+ > seul `npx nodefony env` la montre.
24
27
 
25
28
  ## 2. Deux grammaires, et elles ne font pas la même chose
26
29
 
@@ -54,6 +57,27 @@ booléen, un nombre devient un nombre, `[…]` et `{…}` sont lus en JSON, une
54
57
  devient un tableau, le reste reste une chaîne. C'est ce qui évite le piège classique où la chaîne
55
58
  `"false"` est convertie en booléen **vrai**.
56
59
 
60
+ ### `NF_<MODULE>_<RÉGLAGE>` — les noms courts des modules
61
+
62
+ Un module déclare sur un champ de son schéma le nom court qui le règle (`NF_REDIS_HOST`,
63
+ `NF_REALTIME_DRIVER`…). `npx nodefony env --example` les écrit dans `.env.example`, une section
64
+ par module. Trois règles, toutes au démarrage :
65
+
66
+ - La valeur est **validée par le schéma** : `NF_REDIS_PORT=abc` arrête le démarrage (78) en
67
+ nommant la VARIABLE, pas le seul champ.
68
+ - **Nom court ET `NF__…` sur le même champ** : refus du démarrage en production (78) ; en
69
+ développement, `NF__…` l'emporte avec un avertissement qui nomme les deux.
70
+ - Chaque nom court accepte `<NOM>_FILE` ; une variable vide vaut absente.
71
+
72
+ ### Lire la notice `.env.example`
73
+
74
+ Chaque variable y porte ses métadonnées : `@required` / `@optional`, `@required=forEnv(production)`,
75
+ `@requiredWhen="…"` (exigée sous condition — `NF_JWT_KEYSET` : « en production, si l'application
76
+ émet des jetons »), `@sensitive`, `@default`, et **`@placement`**, qui dit où la poser en
77
+ production : `platform` (variables du déploiement), `secrets` (gestionnaire de secrets, ou
78
+ `<NOM>_FILE`), `workstation` (le poste seulement — ne la pose pas en production).
79
+ `npm run env:check` échoue (78) quand la notice a dérivé d'`env.ts`.
80
+
57
81
  ## 3. Le suffixe `_FILE` — les secrets montés
58
82
 
59
83
  **Toute** variable du catalogue accepte un `_FILE`. Le contenu du fichier est lu, retour à la ligne
@@ -102,23 +126,32 @@ Les seules qui te concernent au déploiement. Le catalogue complet, c'est `npx n
102
126
 
103
127
  ### Données
104
128
 
105
- | Variable | Rôle | Absente ⇒ |
106
- | ----------------- | ---------------------- | ---------------------------------------------------- |
107
- | `NF_DATABASE_URL` | L'URL de la base | **Repli sur SQLite local**, dans `var/` — silencieux |
108
- | `NF_REDIS_URL` | L'URL du cache partagé | Le cache n'est pas chargé |
129
+ | Variable | Rôle | Absente ⇒ |
130
+ | ----------------- | ---------------------- | ------------------------------------------------- |
131
+ | `NF_DATABASE_URL` | L'URL de la base | **Repli sur SQLite local**, dans `var/` — annoncé |
132
+ | `NF_REDIS_URL` | L'URL du cache partagé | Le cache n'est pas chargé |
109
133
 
110
134
  > Le repli SQLite est le piège d'exploitation classique : l'application **démarre**, sert des
111
- > requêtes, écrit dans un fichier — et chaque exemplaire a sa propre base. Rien n'est en erreur.
112
- > `npx nodefony env` le montre en une ligne.
113
-
114
- ### Secrets — obligatoires en production
115
-
116
- | Variable | Ce qu'il protège |
117
- | ------------------- | ------------------------------------------------ |
118
- | `NF_CSRF_SECRET` | Les jetons anti-CSRF, partagés entre exemplaires |
119
- | `NF_TOTP_KEY` | Les secrets de double authentification au repos |
120
- | `NF_WEBHOOK_KEY` | Les signatures de webhooks |
121
- | `NF_ADMIN_PASSWORD` | Le compte d'administration |
135
+ > requêtes, écrit dans un fichier — et chaque exemplaire a sa propre base. Il est ANNONCÉ
136
+ > (avertissement au démarrage, point `DB_SQLITE_FALLBACK` dans `npx nodefony status --json`),
137
+ > mais rien n'échoue : lis le bilan du démarrage. Seule exception : si une entité est écrite pour
138
+ > un autre moteur (PostgreSQL, MySQL), le repli est **refusé** et le démarrage s'arrête.
139
+
140
+ ### Secrets — ce qui se passe en production sans eux
141
+
142
+ | Variable | Ce qu'il protège | Absente en production ⇒ |
143
+ | ------------------- | ------------------------------------------------ | --------------------------------------------------- |
144
+ | `NF_CSRF_SECRET` | Les jetons anti-CSRF, partagés entre exemplaires | **Refus de démarrer** (78) |
145
+ | `NF_JWT_KEYSET` | La clé de signature des jetons, commune à tous | **Refus de démarrer** si l'app émet des jetons |
146
+ | `NF_TOTP_KEY` | Les secrets de double authentification au repos | Double authentification désactivée (journal CRITIC) |
147
+ | `NF_WEBHOOK_KEY` | Les signatures de webhooks | Webhooks désactivés (journal CRITIC) |
148
+ | `NF_ADMIN_PASSWORD` | Le compte d'administration, au PREMIER démarrage | Aucun compte créé ; inutile une fois le compte créé |
149
+
150
+ `NF_JWT_KEYSET` manque presque toujours au premier déploiement : sans elle, un exemplaire qui
151
+ émet des jetons **refuse de démarrer** en production (78) — et deux exemplaires à clés
152
+ différentes refuseraient chacun les jetons de l'autre. `npx nodefony security:secrets --jwt-keyset`
153
+ la produit (une ligne JSON) ; `--env` rend les autres secrets en lignes `CLÉ=valeur`, prêtes pour
154
+ `docker run --env-file` ou un gestionnaire de secrets.
122
155
 
123
156
  Tous en `_FILE` en production. Détail et rotation → [`secrets.md`](secrets.md).
124
157
 
@@ -129,7 +162,7 @@ Tous en `_FILE` en production. Détail et rotation → [`secrets.md`](secrets.md
129
162
  | `NF_POD_NAME` | Identité de l'exemplaire — **dérive l'identité d'origine du bus temps réel** |
130
163
  | `NF_INSTANCE_ID` | Identifiant d'exemplaire |
131
164
  | `NF_REALTIME_DRIVER` | Le transport du bus entre exemplaires |
132
- | `NF_REALTIME_BACKPLANE_SECRET` | Scelle les enveloppes échangées sur le bus |
165
+ | `NF_REALTIME_BACKPLANE_SECRET` | Scelle les enveloppes du bus — moins de 32 caractères arrête le démarrage |
133
166
  | `NF_REALTIME_BACKPLANE_NAMESPACE` | Cloisonne deux applications qui partagent le même bus |
134
167
 
135
168
  > 🔴 **Deux applications sur un même cache, sans espace de noms distinct, se parlent.** Le symptôme
@@ -259,10 +259,13 @@ neuve ne prouve RIEN de tout ça — elle est vide.
259
259
  ```bash
260
260
  # sqlite : une copie du fichier. Le chemin par défaut du connecteur `default` est
261
261
  # `var/databases/nodefony-drizzle.db` — `orm:migrate:status --json` le confirme.
262
- cp var/databases/nodefony-drizzle.db /tmp/essai.sqlite
262
+ # La copie va sous `tmp/scratch/` de l'application, pas dans le /tmp du système :
263
+ # c'est un résultat qu'on relit, et il doit disparaître avec le reste de `tmp/`.
264
+ mkdir -p tmp/scratch
265
+ cp var/databases/nodefony-drizzle.db tmp/scratch/essai.sqlite
263
266
  # PostgreSQL : CREATE DATABASE app_essai TEMPLATE app; (ou une restauration de sauvegarde)
264
267
 
265
- export NF_MIGRATE_DATABASE_URL="sqlite:/tmp/essai.sqlite"
268
+ export NF_MIGRATE_DATABASE_URL="sqlite:tmp/scratch/essai.sqlite"
266
269
  npx nodefony orm:migrate --json
267
270
  ```
268
271
 
@@ -113,25 +113,26 @@ n'a jamais les deux — c'est le discriminant à tester.
113
113
 
114
114
  ### Refus d'usage — la demande elle-même
115
115
 
116
- | Code | Ce qui s'est passé | Le geste |
117
- | -------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
118
- | `NF_MIGRATE_UNKNOWN_CONNECTOR` | aucun connecteur de ce nom | le message liste ceux que l'application déclare |
119
- | `NF_MIGRATE_UNKNOWN_TAG` | `--up-to` désigne une migration inconnue | relire `sources[].pendingTags` |
120
- | `NF_MIGRATE_UNKNOWN_SOURCE` | `--source` n'est pas déclarée par cette application | relire `sources[].name` |
121
- | `NF_MIGRATE_URL_MISMATCH` | la variable de migration désigne une base d'un AUTRE dialecte | corriger la variable, ou choisir le bon connecteur |
122
- | `NF_MIGRATE_NOT_CONFIGURED` | connecteur SQL non déclaré dans la configuration | le déclarer pour pouvoir le suivre |
123
- | `NF_MIGRATE_SECONDARY_CONNECTOR` | un connecteur secondaire porte le nom d'un dossier de dialecte (`sqlite`, `postgres`, `mysql`, `meta`) | renommer le connecteur dans `connectors` — un secondaire range ses migrations sous `migrations/<connecteur>/` |
124
- | `NF_MIGRATE_NO_MIGRATIONS` | ce connecteur est porté par une base qui ne se migre pas ainsi | rien à migrer ici — ce n'est pas une panne |
125
- | `NF_MIGRATE_NOT_DEVELOPMENT` | geste réservé au développement, demandé ailleurs | passer par le travail de déploiement |
126
- | `NF_MIGRATE_DESTRUCTIVE` | des migrations en attente SUPPRIMENT des données | relire le fichier produit, puis assumer explicitement |
127
- | `NF_MIGRATE_UNAVAILABLE` | la commande s'est arrêtée sans pouvoir nommer la cause | constater l'état (`orm:migrate:status`) avant de reprendre |
128
- | `NF_MIGRATE_CONFIRM_REQUIRED` | `orm:reset` demandé hors terminal, ou en sortie machine | relancer avec `--yes` si l'effacement est voulu |
129
- | `NF_GENERATE_NAME` | le nom de migration demandé n'est pas utilisable | reprendre la suggestion que le message donne |
130
- | `NF_GENERATE_MISSING_ENTITY` | une entité est enregistrée sans fichier qui la fournisse | `nodefony inspect entities --json` pour la situer |
131
- | `NF_GENERATE_FRAMEWORK_TABLE` | une entité de l'application usurpe une table du framework | la renommer, ou écrire une migration libre (`--custom`) |
132
- | `NF_GENERATE_DESTRUCTIVE` | la migration ÉCRITE supprime des données | relire le fichier, puis annuler (`git checkout`) ou appliquer — la mise en service a sa propre garde |
133
- | `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma | `repair --forget <source>/<tag>` : l'historique affirme une migration jamais exécutée |
134
- | `NF_GENERATE_TOOL_MISSING` | l'outil de génération n'est pas installé | l'installer en dépendance de développement |
116
+ | Code | Ce qui s'est passé | Le geste |
117
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
118
+ | `NF_MIGRATE_UNKNOWN_CONNECTOR` | aucun connecteur de ce nom | le message liste ceux que l'application déclare |
119
+ | `NF_MIGRATE_UNKNOWN_TAG` | `--up-to` désigne une migration inconnue | relire `sources[].pendingTags` |
120
+ | `NF_MIGRATE_UNKNOWN_SOURCE` | `--source` n'est pas déclarée par cette application | relire `sources[].name` |
121
+ | `NF_MIGRATE_URL_MISMATCH` | la variable de migration désigne une base d'un AUTRE dialecte | corriger la variable, ou choisir le bon connecteur |
122
+ | `NF_MIGRATE_NOT_CONFIGURED` | connecteur SQL non déclaré dans la configuration | le déclarer pour pouvoir le suivre |
123
+ | `NF_MIGRATE_SECONDARY_CONNECTOR` | un connecteur secondaire porte le nom d'un dossier de dialecte (`sqlite`, `postgres`, `mysql`, `meta`) | renommer le connecteur dans `connectors` — un secondaire range ses migrations sous `migrations/<connecteur>/` |
124
+ | `NF_MIGRATE_NO_MIGRATIONS` | ce connecteur est porté par une base qui ne se migre pas ainsi | rien à migrer ici — ce n'est pas une panne |
125
+ | `NF_MIGRATE_NOT_DEVELOPMENT` | geste réservé au développement, demandé ailleurs | passer par le travail de déploiement |
126
+ | `NF_MIGRATE_DESTRUCTIVE` | des migrations en attente SUPPRIMENT des données | relire le fichier produit, puis assumer explicitement |
127
+ | `NF_MIGRATE_UNAVAILABLE` | la commande s'est arrêtée sans pouvoir nommer la cause | constater l'état (`orm:migrate:status`) avant de reprendre |
128
+ | `NF_MIGRATE_CONFIRM_REQUIRED` | `orm:reset` demandé hors terminal, ou en sortie machine | relancer avec `--yes` si l'effacement est voulu |
129
+ | `NF_GENERATE_NAME` | le nom de migration demandé n'est pas utilisable | reprendre la suggestion que le message donne |
130
+ | `NF_GENERATE_MISSING_ENTITY` | une entité est enregistrée sans fichier qui la fournisse | `nodefony inspect entities --json` pour la situer |
131
+ | `NF_GENERATE_UNREADABLE_ENTITY` | rien d'écrit, et un fichier d'entités ne s'importe pas : sa table n'est vue par personne | corriger la cause nommée (souvent un paquet absent des dépendances), ou sortir le fichier de `nodefony/entity/` |
132
+ | `NF_GENERATE_FRAMEWORK_TABLE` | une entité de l'application usurpe une table du framework | la renommer, ou écrire une migration libre (`--custom`) |
133
+ | `NF_GENERATE_DESTRUCTIVE` | la migration ÉCRITE supprime des données | relire le fichier, puis annuler (`git checkout`) ou appliquer — la mise en service a sa propre garde |
134
+ | `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma | `repair --forget <source>/<tag>` : l'historique affirme une migration jamais exécutée |
135
+ | `NF_GENERATE_TOOL_MISSING` | l'outil de génération n'est pas installé | l'installer en dépendance de développement |
135
136
 
136
137
  ## Un contrat qui ne bougera pas
137
138