@nodefony/documentation 10.0.0-alpha.1

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 (34) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +168 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
  6. package/dist/index.js +75 -0
  7. package/dist/nodefony/config/config.js +71 -0
  8. package/dist/nodefony/config/defineModuleConfig.js +49 -0
  9. package/dist/nodefony/controller/DocumentationController.js +102 -0
  10. package/dist/nodefony/interfaces/IDocumentation.js +1 -0
  11. package/dist/nodefony/interfaces/index.js +1 -0
  12. package/dist/nodefony/service/DocumentationService.js +421 -0
  13. package/dist/nodefony/src/docScanner.js +58 -0
  14. package/dist/nodefony/src/errors/DocumentationError.js +45 -0
  15. package/dist/nodefony/src/frontmatter.js +63 -0
  16. package/dist/nodefony/src/linkResolver.js +68 -0
  17. package/dist/nodefony/src/search.js +120 -0
  18. package/dist/nodefony/src/slug.js +62 -0
  19. package/dist/types/index.d.ts +58 -0
  20. package/dist/types/nodefony/config/config.d.ts +37 -0
  21. package/dist/types/nodefony/config/defineModuleConfig.d.ts +17 -0
  22. package/dist/types/nodefony/controller/DocumentationController.d.ts +33 -0
  23. package/dist/types/nodefony/interfaces/IDocumentation.d.ts +144 -0
  24. package/dist/types/nodefony/interfaces/index.d.ts +5 -0
  25. package/dist/types/nodefony/service/DocumentationService.d.ts +78 -0
  26. package/dist/types/nodefony/src/docScanner.d.ts +47 -0
  27. package/dist/types/nodefony/src/errors/DocumentationError.d.ts +32 -0
  28. package/dist/types/nodefony/src/frontmatter.d.ts +37 -0
  29. package/dist/types/nodefony/src/linkResolver.d.ts +55 -0
  30. package/dist/types/nodefony/src/search.d.ts +64 -0
  31. package/dist/types/nodefony/src/slug.d.ts +48 -0
  32. package/docs/architecture.md +647 -0
  33. package/docs/index.md +369 -0
  34. package/package.json +81 -0
package/docs/index.md ADDED
@@ -0,0 +1,369 @@
1
+ ---
2
+ title: "@nodefony/documentation — la doc de tes modules, servie par ton serveur"
3
+ navTitle: "@nodefony/documentation"
4
+ lang: fr
5
+ module: "@nodefony/documentation"
6
+ topic: documentation
7
+ section: "Documentation"
8
+ audience: [developer, devops]
9
+ tags:
10
+ [
11
+ documentation,
12
+ portail,
13
+ markdown,
14
+ frontmatter,
15
+ slug,
16
+ data-plane,
17
+ headless,
18
+ studio,
19
+ ]
20
+ version: "doc"
21
+ status: stable
22
+ updated: 2026-07-19
23
+ source: "src/packages/@nodefony/documentation/docs/index.md"
24
+ coverageModule: documentation
25
+ ---
26
+
27
+ # @nodefony/documentation — la doc de tes modules, servie par ton serveur
28
+
29
+ > Chaque module range sa documentation à côté de son code, dans son propre dossier `docs/`. Ce
30
+ > module fait le tour de tous ces dossiers — les tiens, ceux du framework, et même ceux des paquets
31
+ > installés mais pas encore activés — en dresse un **catalogue unique**, et le sert en JSON sous
32
+ > `/nodefony/documentation/api/*`. Il ne rend aucune page : il produit de la donnée, que le portail
33
+ > de Studio (ou ton propre générateur de site) transforme en pages. C'est ce qui permet à la doc
34
+ > d'un module d'arriver **avec le paquet npm**, sans site à déployer ni index à tenir à jour.
35
+
36
+ 📍 [Documentation](../../../../../docs/index.md) › **@nodefony/documentation**
37
+
38
+ ## 🧭 Par où commencer
39
+
40
+ Trois parcours selon ce que tu viens faire. L'ordre compte : chaque étape suppose la précédente.
41
+
42
+ **J'écris la documentation de mon module** — la doc qui voyagera avec le paquet.
43
+
44
+ 1. [ADR-0001 — où poser un fichier](../../../../../docs/adr/0001-docs-modules-emplacement-hybride.md) —
45
+ dans le module ou à la racine. C'est la première décision, et celle qu'on défait le plus mal :
46
+ déplacer une page change son identifiant, donc tous les liens qui y menaient.
47
+ 2. [Démarrage rapide](#-démarrage-rapide) — déclarer le module, écrire la page, la voir apparaître.
48
+ L'étape 2 porte le **contrat de frontmatter** : les six clés réellement lues par le serveur.
49
+ 3. [Ce que le module apporte](#-ce-que-le-module-apporte) — les quatre propriétés qui expliquent
50
+ pourquoi une page atterrit où elle atterrit, et pourquoi un `index.md` ouvre toujours sa section.
51
+ 4. [Architecture interne](./architecture.md) — le trajet complet du fichier au portail, si tu veux
52
+ comprendre plutôt que suivre la recette.
53
+
54
+ **Je publie la documentation de mon application** — un portail interne, sans déployer de site.
55
+
56
+ 1. [Démarrage rapide](#-démarrage-rapide) — le module se déclare comme n'importe quel autre, et
57
+ **avant** Studio : le portail consomme son data plane.
58
+ 2. [Configuration](#-configuration) — ce qui est scanné (`docs/` racine, modules chargés, paquets
59
+ installés) et vers quel dépôt pointe le lien « Modifier » de chaque page.
60
+ 3. [Observabilité — Studio](#-observabilité--studio) — les deux portes du data plane et le rôle
61
+ qu'il faut porter pour les ouvrir. Elles répondent aussi en `curl`, sans interface.
62
+ 4. [`@nodefony/studio`](../../studio/docs/index.md) — la surface qui rend ces pages ; elle ne fait
63
+ que consommer ce que le module publie.
64
+
65
+ **Un lien tombe à côté, une page reste introuvable** — le dépannage le plus fréquent.
66
+
67
+ 1. [Ce que le module apporte](#-ce-que-le-module-apporte), propriété « tes liens relatifs restent
68
+ valides des deux côtés » — un lien non traduit signifie presque toujours une cible **hors de
69
+ l'index**, pas un bug de rendu.
70
+ 2. [Architecture interne](./architecture.md) — la table chemin → identifiant, seule à savoir à quoi
71
+ correspond un `../index.md`, et pourquoi elle vit côté serveur.
72
+ 3. [Tests & couverture](#-tests--couverture) — un banc rejoue la navigation sur le corpus **réel**
73
+ du dépôt : il attrape le `../` en trop qu'aucune relecture ne voit.
74
+
75
+ ## 🗂️ Les pages à lire
76
+
77
+ Le tableau pour choisir en cinq secondes ; les cards en dessous pour savoir ce qu'on y trouve. Ce
78
+ module est volontairement mince : une seule page de brique, plus deux repères transverses qui
79
+ décident **où** ta doc doit vivre.
80
+
81
+ | Page | Ce qu'elle résout | Tu en as besoin quand… |
82
+ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------ |
83
+ | [Architecture interne](./architecture.md) | le trajet d'un `.md` : scan, cache, identifiant, liens | une page manque, ou tu branches un autre lecteur |
84
+ | [ADR-0001 — emplacement des docs](../../../../../docs/adr/0001-docs-modules-emplacement-hybride.md) | module ou racine : la règle de placement, et pourquoi | tu crées la documentation d'un module |
85
+ | [Le portail général](../../../../../docs/index.md) | le catalogue de toute la doc, rangé par type | tu cherches une page dont tu ignores le module |
86
+
87
+ ```nodefony-cards
88
+ [
89
+ { "icon": "🏗️", "title": "architecture", "href": "architecture.md",
90
+ "desc": "Le scan des sources, le cache d'index et sa durée de vie, la fabrication de l'identifiant de page, la traduction des liens relatifs, et la garde anti-traversée qui protège la lecture de fichiers. À ouvrir quand le portail ne montre pas ce que tu attends : elle explique à quel étage la chose s'est perdue.",
91
+ "meta": "une page manque, ou tu branches un autre lecteur" },
92
+ { "icon": "🏛️", "title": "ADR-0001 — emplacement des docs", "href": "../../../../../docs/adr/0001-docs-modules-emplacement-hybride.md",
93
+ "desc": "La décision d'architecture qui fonde ce module : la doc d'un module vit dans le module, le transverse reste à la racine. Elle dit aussi comment une page est versionnée — frontmatter et git.",
94
+ "meta": "à lire avant de créer ton premier docs/, pas après" },
95
+ { "icon": "🗂️", "title": "le portail général", "href": "../../../../../docs/index.md",
96
+ "desc": "L'accueil de toute la documentation Nodefony, en cards par famille : fondations, cœur, sécurité, données, temps réel, interface. C'est ce que ce module sert, vu depuis le lecteur.",
97
+ "meta": "tu cherches une page dont tu ignores le module" }
98
+ ]
99
+ ```
100
+
101
+ ## 🧩 Ce que le module apporte
102
+
103
+ Quatre propriétés, toutes vérifiables dans le code — c'est ce qui distingue ce module d'un
104
+ `readFile` sur un dossier.
105
+
106
+ **La doc voyage avec le code qu'elle décrit.** Le service scanne le `docs/` racine du projet **et**
107
+ le `docs/` de chaque module chargé (`DocumentationService.#scanAll()`, `DocumentationService.ts:231`).
108
+ Pour ton module, la seule condition est d'avoir déclaré `docs` dans le champ `files` de son
109
+ `package.json` — sans quoi npm ne publie pas le dossier, et la doc disparaît à l'installation.
110
+ Le regroupement en sections ne se déclare nulle part : il est **calculé depuis le dossier parent**
111
+ du fichier (`group`, `docScanner.ts:76`), et l'`index.md` d'un dossier est présenté en premier
112
+ (`DocumentationService.#orderPages()`, `DocumentationService.ts:486`) — un point d'entrée trié
113
+ alphabétiquement se retrouverait au milieu de ses propres pages.
114
+
115
+ **La doc d'un module non activé est lisible quand même.** Les paquets présents dans
116
+ `node_modules/@nodefony/*` sont scannés même s'ils ne figurent pas dans le manifeste de
117
+ l'application (`DocumentationService.#installedDocDirs()`, `DocumentationService.ts:386`). C'est
118
+ précisément le moment où on lit la doc d'un module : pour décider de l'activer. Les chemins sont
119
+ résolus en lien réel, donc un dépôt en espace de travail indexe la source, jamais le lien
120
+ symbolique — sinon le même fichier existerait sous deux chemins, et ses liens ne résoudraient plus.
121
+
122
+ **Un identifiant de page est une clé, jamais un chemin.** Servir une page consiste à retrouver son
123
+ entrée par **égalité d'identifiant** dans le catalogue scanné, puis à ouvrir le chemin absolu déjà
124
+ connu (`DocumentationService.getPage()`, `DocumentationService.ts:151`). Le `mod~http~index` reçu du
125
+ client n'est jamais concaténé à un chemin de système de fichiers. Une garde en défense de profondeur
126
+ (`isSafeSlug()`, `slug.ts:39`) rejette en plus tout identifiant suspect — segment `..`, séparateur,
127
+ octet nul, hors jeu de caractères — **avant** même la recherche.
128
+
129
+ **Tes liens relatifs restent valides des deux côtés.** Une page se lie à ses voisines par chemin
130
+ relatif (`[Architecture](./architecture.md)`), ce qui la rend lisible sur GitHub et dans l'éditeur ;
131
+ le portail, lui, navigue par identifiant. La traduction est faite au service
132
+ (`rewriteInternalLinks()`, `linkResolver.ts:90`), seul à connaître la table chemin → identifiant.
133
+ Une cible **absente de l'index** est laissée intacte plutôt que réécrite au hasard : mieux vaut un
134
+ lien inerte qu'un identifiant inventé.
135
+
136
+ > [!IMPORTANT]
137
+ > **Le module ne rend aucun HTML.** Il produit deux formes de données, `IDocTree`
138
+ > (`IDocumentation.ts:57`) et `IDocPage` (`IDocumentation.ts:67`), et s'arrête là. Conséquence
139
+ > pratique : tout ce que montre le portail est aussi lisible en `curl`, en script, ou par un agent —
140
+ > et le même data plane alimentera un générateur de site statique ou une indexation documentaire
141
+ > sans qu'une ligne du module change. Le rendu appartient au lecteur, jamais au serveur.
142
+
143
+ Le module se déclare par ailleurs **non critique** (`Documentation.critical`, `index.ts:33`) : un
144
+ échec de son démarrage n'emporte jamais le processus. On perd le catalogue, jamais l'application.
145
+
146
+ ## 🚀 Démarrage rapide
147
+
148
+ Vu depuis une application créée par `nodefony create app`, qui veut publier sa propre documentation
149
+ interne.
150
+
151
+ ### 1. Déclarer le module
152
+
153
+ ```ts
154
+ // nodefony.config.ts — l'orchestrateur de l'application
155
+ export default defineConfig(() => ({
156
+ modules: [
157
+ "@nodefony/http",
158
+ "@nodefony/framework",
159
+ // Le data plane est protégé par rôle : sans pare-feu, personne ne porte
160
+ // le rôle qui ouvre /nodefony/documentation/api/*.
161
+ "@nodefony/security",
162
+ use("@nodefony/documentation", {
163
+ // `docs/` à la racine du projet = la doc transverse de TON application.
164
+ scan: { rootDir: "docs", includeModules: true, includeInstalled: true },
165
+ // Le lien « Modifier » de chaque page pointera vers TON dépôt.
166
+ repo: { url: "https://github.com/acme/boutique", editPathPrefix: "blob" },
167
+ // 0 = rescan à chaque appel : un nouveau `.md` apparaît sans redémarrer.
168
+ // En production, garder le défaut (30 s) — le scan touche le disque.
169
+ cache: { ttlMs: 0 },
170
+ }),
171
+ // Studio APRÈS : son portail consomme le data plane déclaré au-dessus.
172
+ "@nodefony/studio",
173
+ ],
174
+ }));
175
+ ```
176
+
177
+ ### 2. Écrire une page
178
+
179
+ Un fichier `.md` dans `docs/` (ou `<ton-module>/docs/`), ouvert par un bloc de métadonnées. Le
180
+ parseur est un **YAML plat** volontairement restreint (`parseFrontmatter()`, `frontmatter.ts:51`) :
181
+ clé/valeur, liste en ligne `[a, b]` ou liste en bloc. Ni objets imbriqués, ni valeurs multilignes.
182
+
183
+ ```yaml
184
+ ---
185
+ title: "Facturation — cycle d'une facture"
186
+ audience: [developer]
187
+ status: stable
188
+ updated: 2026-07-19
189
+ version: "1.4.0"
190
+ source: "docs/facturation.md"
191
+ ---
192
+ ```
193
+
194
+ Six clés seulement sont **consommées** par le serveur ; les autres (`tags`, `topic`, `module`…) sont
195
+ conservées telles quelles, sans effet sur le catalogue — elles servent à l'indexation documentaire.
196
+
197
+ | Clé | Ce qu'elle change | À défaut |
198
+ | ---------- | ----------------------------------------------------- | -------------------------------------------- |
199
+ | `title` | le titre affiché dans le catalogue et en tête de page | le nom de fichier, humanisé |
200
+ | `audience` | les personas qui voient la page (filtre de vue) | vide = visible par toutes |
201
+ | `status` | le badge de maturité affiché à côté du titre | aucun badge |
202
+ | `version` | la version montrée pour la page | `"doc"` |
203
+ | `updated` | la date de fraîcheur affichée | aucune date |
204
+ | `source` | le chemin dépôt qui construit le lien « Modifier » | le chemin réel du fichier, relatif au projet |
205
+
206
+ Les valeurs de `audience` et de `status` sont des énumérations fermées, `DocAudience`
207
+ (`IDocumentation.ts:10`) et `DocStatus` (`IDocumentation.ts:13`) : toute valeur hors liste est
208
+ **silencieusement écartée**, jamais affichée telle quelle.
209
+
210
+ > [!WARNING]
211
+ > **Deux pièges coûtent une page mal rangée.** La date se déclare `updated` — un `last-updated`
212
+ > n'est pas lu, et la page paraît sans fraîcheur. Et une clé `section` dans le frontmatter ne
213
+ > regroupe rien : le regroupement vient du **dossier parent** du fichier (`group`,
214
+ > `docScanner.ts:76`). Pour ranger une page ailleurs, on la déplace ; on ne la renomme pas.
215
+
216
+ ### 3. La lire
217
+
218
+ ```bash
219
+ # L'index complet : sections, pages, personas. Un compte porteur du rôle suffit.
220
+ curl -k --cookie-jar /tmp/j -b /tmp/j \
221
+ https://127.0.0.1:5152/nodefony/documentation/api/tree
222
+
223
+ # Une page précise, markdown résolu + lien « Modifier » assemblé côté serveur.
224
+ curl -k -b /tmp/j \
225
+ https://127.0.0.1:5152/nodefony/documentation/api/page/root~facturation
226
+ ```
227
+
228
+ Ce qu'on observe : `…/api/tree` renvoie les sections dans l'ordre — la racine d'abord, puis un
229
+ groupe par module — chaque section ouverte par son `index.md`. `…/api/page/{slug}` renvoie le
230
+ markdown **sans son bloc de métadonnées**, variables résolues et liens internes traduits. Un
231
+ identifiant inconnu ou rejeté répond un 404 volontairement muet (`{slug, error}`) : le détail reste
232
+ dans les journaux du serveur. La même page s'affiche dans Studio sur `/nodefony/documentation`.
233
+
234
+ ## 🏛️ Place dans le framework
235
+
236
+ ```mermaid
237
+ flowchart TD
238
+ ROOT["docs/ (racine)<br/>guides · décisions · transverse"]
239
+ MODS["&lt;module&gt;/docs/*.md<br/>modules chargés"]
240
+ PKGS["node_modules/@nodefony/*/docs<br/>paquets installés, même inactifs"]
241
+ SVC["DocumentationService<br/>scan · cache · index · variables"]
242
+ CTRL["DocumentationController<br/>/nodefony/documentation/api/*"]
243
+ SEC["@nodefony/security<br/>rôle exigé par endpoint"]
244
+ UI["@nodefony/studio<br/>portail /nodefony/documentation"]
245
+ OTHER["Autres lecteurs<br/>site statique · indexation · curl"]
246
+ ROOT --> SVC
247
+ MODS --> SVC
248
+ PKGS --> SVC
249
+ SVC --> CTRL
250
+ SEC -.->|protège| CTRL
251
+ CTRL --> UI
252
+ CTRL --> OTHER
253
+ ```
254
+
255
+ Le module s'appuie sur `@nodefony/framework` pour le routage et sur `@nodefony/http` pour le
256
+ contexte de requête ; il n'impose aucune base de données et n'écrit rien. La flèche ne part jamais
257
+ dans l'autre sens : aucun module ne dépend de lui pour fonctionner, et Studio n'en est qu'un
258
+ consommateur parmi d'autres.
259
+
260
+ ## 🧰 Surface publique
261
+
262
+ Côté serveur, le module expose `DocumentationService` — sa méthode `getTree()`
263
+ (`DocumentationService.ts:185`) construit le catalogue, `getPage()`
264
+ (`DocumentationService.ts:244`) sert une page, `invalidate()` (`DocumentationService.ts:177`) force
265
+ un rescan immédiat, et `registerVar()` (`DocumentationService.ts:138`) branche une variable
266
+ dynamique.
267
+
268
+ Les variables sont la seule extension du module. Une page écrit `{{ nom }}` ; le serveur substitue
269
+ la valeur au moment de servir (`DocumentationService.#resolveVars()`,
270
+ `DocumentationService.ts:541`). Trois variables sont fournies d'office — version du noyau, branche
271
+ et empreinte git — enregistrées quand tous les modules sont montés
272
+ (`Documentation.onKernelReady()`, `index.ts:70`). Ton module peut ajouter les siennes :
273
+
274
+ ```ts
275
+ // Dans le hook onKernelReady de ton module : tous les services existent.
276
+ const docs = this.get<IDocumentationService>("documentation");
277
+ docs?.registerVar("tarif-socle", () => "29 € / mois");
278
+ ```
279
+
280
+ Une variable sans fournisseur est **laissée telle quelle** dans la page : l'auteur voit qu'il manque
281
+ un branchement, au lieu d'un trou silencieux. Un fournisseur qui échoue ne casse jamais le rendu.
282
+
283
+ Le module publie aussi ses briques pures, utilisables hors serveur — `parseFrontmatter()`,
284
+ `scanDocsDir()` (`docScanner.ts:55`), `isSafeSlug()` et `pathToSlug()` (`slug.ts:60`) — de quoi
285
+ écrire un générateur de site qui range les fichiers exactement comme le portail. Les signatures
286
+ exactes vivent dans le graphe généré (`jq '.symbols.DocumentationService' .ai/symbols.json`), jamais
287
+ recopiées ici : elles divergeraient en silence.
288
+
289
+ ## ⚙️ Configuration
290
+
291
+ Un seul point d'entrée : `use("@nodefony/documentation", { … })` dans `nodefony.config.ts`, validé
292
+ au démarrage contre le schéma du module (`documentationConfigSchema`,
293
+ `nodefony/config/config.ts:134`). Quatre blocs :
294
+
295
+ | Bloc | Ce qu'il décide | Défaut d'usine |
296
+ | --------- | ------------------------------------------------------------------------------------- | ------------------------------- |
297
+ | `enabled` | active le data plane ; `false` = module chargé mais inerte | `true` |
298
+ | `scan` | les sources indexées : dossier racine, modules chargés, paquets installés, exclusions | `docs` · tout activé |
299
+ | `repo` | le dépôt visé par le lien « Modifier » d'une page, et la forme du lien | dépôt Nodefony · segment `edit` |
300
+ | `cache` | la durée de vie du catalogue ; `0` = rescan à chaque appel | `30000` ms |
301
+
302
+ Deux réglages se surchargent par l'environnement, appliqués **après** la validation
303
+ (`defineDocumentationConfig()`, `defineModuleConfig.ts:32`) : `DOCS_REPO_URL` et `DOCS_REPO_BRANCH`.
304
+ Le second sert en conteneur, où le dépôt git n'est pas embarqué — sans lui, la branche est lue au
305
+ runtime dans le dépôt réel, et retombe sur `main` s'il n'y en a pas.
306
+
307
+ > [!TIP]
308
+ > **Le cache ne porte que le catalogue, jamais le contenu.** Une page est relue à chaque demande
309
+ > (`DocumentationService.#ensureCache()`, `DocumentationService.ts:298`) : corriger une phrase se
310
+ > voit au rafraîchissement. C'est **ajouter ou supprimer un fichier** qui attend l'expiration — d'où
311
+ > `ttlMs: 0` en développement, et le défaut en production.
312
+
313
+ ## 📡 Observabilité — Studio
314
+
315
+ Le portail vit sur `/nodefony/documentation` : l'arbre des sections à gauche, la page rendue au
316
+ centre, le sommaire et le lien « Modifier » à droite. La page du module,
317
+ `/nodefony/modules/documentation`, montre par ailleurs sa configuration résolue, ses routes et ses
318
+ symboles.
319
+
320
+ Deux portes composent le data plane, toutes deux réservées aux rôles de développement et de
321
+ supervision (`DocumentationController.ts:48`) — la documentation technique n'est pas une page
322
+ publique :
323
+
324
+ | Route | Ce qu'elle renvoie |
325
+ | -------------------------------------- | -------------------------------------------------------------------------- |
326
+ | `GET /nodefony/documentation/api/tree` | le catalogue : sections, pages, personas (`DocumentationController.ts:50`) |
327
+ | `GET …/api/page/{slug}` | une page résolue + son lien source (`DocumentationController.ts:65`) |
328
+
329
+ Le lien « Modifier » est assemblé côté serveur à partir d'un chemin **relatif** au dépôt
330
+ (`DocumentationService.#buildSourceUrl()`, `DocumentationService.ts:560`) : aucun chemin absolu de
331
+ système de fichiers ne sort jamais du serveur.
332
+
333
+ ## 🧪 Tests & couverture
334
+
335
+ Les compteurs sont régénérés depuis vitest, jamais figés dans cette prose. Ce qui mérite d'être dit
336
+ ici, c'est **ce que les suites prouvent** — et la frontière volontaire de ce qu'elles ne couvrent pas.
337
+
338
+ | Type | Où | Ce qui est prouvé |
339
+ | -------------------- | ------------------------------------------ | ---------------------------------------------------------------------- |
340
+ | Métadonnées | `nodefony/tests/unit/frontmatter.test.ts` | YAML plat : listes, quotes, absence de bloc, clés déclarées vides |
341
+ | Identifiants | `nodefony/tests/unit/slug.test.ts` | fabrication et garde anti-traversée, jeu de caractères, bornes |
342
+ | Découverte | `nodefony/tests/unit/docScanner.test.ts` | dossier absent, exclusions par segment, titre déduit du nom de fichier |
343
+ | Traduction des liens | `nodefony/tests/unit/linkResolver.test.ts` | remontées relatives, ancres, cibles hors index laissées intactes |
344
+ | Navigation du corpus | `nodefony/tests/unit/corpusLinks.test.ts` | les liens des **vraies** pages du dépôt résolvent tous |
345
+
346
+ Le dernier est le plus utile au quotidien : il rejoue la navigation sur le corpus réel plutôt que
347
+ sur un index fabriqué, et attrape ce qu'aucun test à double ne voit — un `../` en trop, une page
348
+ renommée, une cible supprimée. La frontière est délibérée : le service et le contrôleur dépendent du
349
+ noyau et du conteneur, ils relèvent donc de l'intégration sur serveur vivant, pas du run unitaire.
350
+
351
+ ```bash
352
+ cd src/packages/@nodefony/documentation
353
+ npm test # suite unitaire, sans serveur
354
+ npm run coverage # + rapport lisible dans l'onglet Couverture de Studio
355
+ ```
356
+
357
+ ## 🔗 Pour aller plus loin
358
+
359
+ - ⬆️ **Remonter** : [Toute la documentation](../../../../../docs/index.md)
360
+ - 📄 **La page du module** : [Architecture interne — du fichier au portail](./architecture.md)
361
+ - 🧭 **Modules voisins** : [`@nodefony/studio`](../../studio/docs/index.md) (le portail qui rend ces
362
+ pages) · [`@nodefony/framework`](../../framework/docs/index.md) (routage et décorateurs) ·
363
+ [`@nodefony/security`](../../security/docs/index.md) (les rôles qui ouvrent le data plane) ·
364
+ [`nodefony`](../../../../../src/nodefony/docs/index.md) (le noyau, ses modules et son cycle de vie)
365
+ - 🏛️ **Transverse** :
366
+ [ADR-0001 — emplacement des docs](../../../../../docs/adr/0001-docs-modules-emplacement-hybride.md) ·
367
+ [vue d'ensemble du framework](../../../../../docs/architecture/vue-ensemble.md) ·
368
+ [configuration](../../../../../docs/architecture/configuration.md)
369
+ - 📖 [Lexique général](../../../../../docs/lexique.md) du framework.
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@nodefony/documentation",
3
+ "version": "10.0.0-alpha.1",
4
+ "description": "Plan de données de documentation de Nodefony : index des pages co-localisées dans chaque module, résolution des variables dynamiques, exposition en lecture pour la console d'administration",
5
+ "contributors": [],
6
+ "main": "./dist/index.js",
7
+ "type": "module",
8
+ "types": "./dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/types/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "start": "node dist/index.js",
18
+ "build": "rimraf dist && rolldown -c rolldown.config.ts && tsgo -p tsconfig.declarations.json",
19
+ "clean": "rimraf dist",
20
+ "dev": "rolldown -c rolldown.config.ts --watch",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "coverage": "vitest run --coverage",
24
+ "typecheck": "tsgo --noEmit -p tsconfig.json && tsgo --noEmit -p tsconfig.tests.json"
25
+ },
26
+ "private": false,
27
+ "engines": {
28
+ "node": ">=24.0.0"
29
+ },
30
+ "keywords": [
31
+ "nodefony",
32
+ "documentation",
33
+ "markdown",
34
+ "docs",
35
+ "typescript",
36
+ "esm"
37
+ ],
38
+ "devDependencies": {
39
+ "@nodefony/framework": "*",
40
+ "@nodefony/http": "*",
41
+ "@types/node": "26.4.1",
42
+ "@vitest/coverage-v8": "5.0.0",
43
+ "nodefony": "*",
44
+ "rimraf": "6.1.3",
45
+ "vitest": "5.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@nodefony/framework": "*",
49
+ "@nodefony/http": "*",
50
+ "nodefony": "*",
51
+ "zod": "^4.4.3"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/nodefony/nodefony-core.git",
56
+ "directory": "src/packages/@nodefony/documentation"
57
+ },
58
+ "license": "CECILL-B",
59
+ "licenses": [
60
+ {
61
+ "type": "CECILL-B",
62
+ "url": "http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html"
63
+ }
64
+ ],
65
+ "author": "Christophe CAMENSULI <ccamensuli@gmail.com>",
66
+ "readmeFilename": "README.md",
67
+ "dependencies": {
68
+ "tslib": "2.8.1"
69
+ },
70
+ "files": [
71
+ "dist",
72
+ "docs"
73
+ ],
74
+ "publishConfig": {
75
+ "access": "public"
76
+ },
77
+ "homepage": "https://nodefony.github.io/nodefony-core/",
78
+ "bugs": {
79
+ "url": "https://github.com/nodefony/nodefony-core/issues"
80
+ }
81
+ }