@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.
- package/LICENSE +544 -0
- package/README.md +168 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +75 -0
- package/dist/nodefony/config/config.js +71 -0
- package/dist/nodefony/config/defineModuleConfig.js +49 -0
- package/dist/nodefony/controller/DocumentationController.js +102 -0
- package/dist/nodefony/interfaces/IDocumentation.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/DocumentationService.js +421 -0
- package/dist/nodefony/src/docScanner.js +58 -0
- package/dist/nodefony/src/errors/DocumentationError.js +45 -0
- package/dist/nodefony/src/frontmatter.js +63 -0
- package/dist/nodefony/src/linkResolver.js +68 -0
- package/dist/nodefony/src/search.js +120 -0
- package/dist/nodefony/src/slug.js +62 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/types/nodefony/config/config.d.ts +37 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +17 -0
- package/dist/types/nodefony/controller/DocumentationController.d.ts +33 -0
- package/dist/types/nodefony/interfaces/IDocumentation.d.ts +144 -0
- package/dist/types/nodefony/interfaces/index.d.ts +5 -0
- package/dist/types/nodefony/service/DocumentationService.d.ts +78 -0
- package/dist/types/nodefony/src/docScanner.d.ts +47 -0
- package/dist/types/nodefony/src/errors/DocumentationError.d.ts +32 -0
- package/dist/types/nodefony/src/frontmatter.d.ts +37 -0
- package/dist/types/nodefony/src/linkResolver.d.ts +55 -0
- package/dist/types/nodefony/src/search.d.ts +64 -0
- package/dist/types/nodefony/src/slug.d.ts +48 -0
- package/docs/architecture.md +647 -0
- package/docs/index.md +369 -0
- 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["<module>/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
|
+
}
|