From 227ecc9e7d98bfc4fe6bd36c717b5841223dbf81 Mon Sep 17 00:00:00 2001 From: Hyko Date: Fri, 24 Apr 2026 17:41:41 -0400 Subject: [PATCH] docs(DEV.md): reorganize and clarify dev standards and build documentation - move promise and env-var principles to top of code standards section - clarify build strategy: bundle:false applies to all src/ files, not just suffixed ones - update RSC boundary explanation to describe module isolation approach - add note about jsx loader handling .js files via esbuild - remove redundant section headers and inline comments in examples - fix grammar in singleton bundling warning --- docs/DEV.md | 51 +++++++++++---------------------------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/docs/DEV.md b/docs/DEV.md index 51b2612..0f5f777 100644 --- a/docs/DEV.md +++ b/docs/DEV.md @@ -18,7 +18,9 @@ Pour les conventions de commit : [COMMITS.md](./dev/COMMITS.md). ## Standards de code -### Principes généraux +**Les promesses ne s'ignorent pas.** Chaque `Promise` est `await`ée ou `.catch()`ée. Une promesse silencieuse qui échoue est un bug invisible. + +**Les variables d'environnement et la documentation se mettent à jour avec le code.** Toute variable ajoutée, renommée ou supprimée doit être reflétée dans `.env.example`. Toute décision architecturale ou convention nouvelle doit être documentée dans le fichier `docs/` concerné. **Une fonction, une responsabilité.** Si elle fait deux choses, c'est deux fonctions. Si elle ne tient pas sur un écran, la découper. @@ -26,14 +28,10 @@ Pour les conventions de commit : [COMMITS.md](./dev/COMMITS.md). **Les données entrantes sont suspectes.** On valide en entrée de fonction. On ne suppose pas que l'appelant a fait le travail. -**Les promesses ne s'ignorent pas.** Chaque `Promise` est `await`ée ou `.catch()`ée. Une promesse silencieuse qui échoue est un bug invisible. - **La portée des variables est minimale.** On déclare au plus près de l'usage. Pas de variables réutilisées pour deux rôles différents. **ESLint passe sans avertissement.** Un warning ignoré aujourd'hui est un bug non détecté demain. -**Les variables d'environnement et la documentation se mettent à jour avec le code.** Toute variable ajoutée, renommée ou supprimée doit être reflétée dans `.env.example`. Toute décision architecturale ou convention nouvelle doit être documentée dans le fichier `docs/` concerné. Le code et sa documentation vieillissent ensemble. - **Les commentaires reflètent toujours le comportement réel du code.** Un commentaire obsolète est pire qu'un commentaire absent — il induit en erreur. Quand on modifie une fonction, on met à jour son commentaire. Un commentaire qui contredit le code est un bug de documentation. --- @@ -54,19 +52,18 @@ Tout fichier épinglé à une frontière Next.js porte le suffixe dans son nom : - pas de suffixe → module neutre, utilisable des deux côtés - `actions.js` → débute par `'use server'` (server actions Next.js) -Ces suffixes ne sont pas cosmétiques : **le build les utilise comme source de vérité**. Tout fichier `*.server.js` ou `*.client.js` est automatiquement ajouté à la config tsup non-bundlée pour préserver la frontière RSC. +Ces suffixes ne sont pas cosmétiques : **le build les utilise comme source de vérité**. Le build compile l'intégralité de `src/` avec `bundle: false` — chaque fichier reste un module séparé, ce qui permet à Next.js de respecter les frontières RSC et `'use client'` sans que le bundler ne fusionne les modules. --- ## Build et configuration tsup -### Source de vérité - -`tsup.config.js` compile **tous les fichiers `.js` et `.jsx` de `src/`** avec `bundle: false`. Chaque fichier devient un module standalone dans `dist/` ; les imports relatifs sont préservés tels quels. +`tsup.config.js` compile **tous les fichiers `.js` et `.jsx` de `src/`** avec `bundle: false`. Chaque fichier devient un module standalone dans `dist/` ; les imports relatifs sont préservés tels quels. La structure `dist/` reflète exactement `src/` grâce à `outbase: 'src'`. - **Ajouter un module public = éditer seulement `package.json#exports`.** L'entry list se régénère au prochain build via un walk de `src/`. -- **Pas de bundling des fichiers internes** : les modules de registre (`registry.js`, etc.) sont des singletons — les bundler avec `bundle: true` dans un barrel créerait une copie inline distincte et casserait le partage d'état entre les pages et les widgets. +- **Pas de bundling des fichiers internes** : les modules de registre (`registry.js`, etc.) sont des singletons — les bundler créerait une copie inline distincte et casserait le partage d'état entre les pages et les widgets. - Les self-imports `@zen/core/*` sont générés automatiquement à partir des clés de `exports` et restent toujours dans la liste `external`. +- Les fichiers `.js` et `.jsx` sont tous traités comme du JSX via esbuild (`loader: { '.js': 'jsx' }`, `jsx: 'automatic'`) — pas besoin d'extension `.jsx` pour écrire du JSX. --- @@ -87,15 +84,12 @@ import OrdersWidget from './admin/OrdersWidget'; import OrdersPage from './admin/OrdersPage'; import { countOrders } from './admin/orders.server'; -// Widget dashboard — fetcher serveur + composant client partagent un même id. registerWidgetFetcher('orders', async () => ({ total: await countOrders() })); registerWidget({ id: 'orders', Component: OrdersWidget, order: 20 }); -// Sidebar registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 }); registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce' }); -// Page — le slug correspond au segment sous /admin/. registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes' }); ``` @@ -108,33 +102,10 @@ import './zen.extensions'; ## Sécurité -### Données entrantes +**Données entrantes** : toute donnée externe est considérée malveillante par défaut. On valide côté serveur uniquement. -Toute donnée externe est considérée malveillante par défaut : requêtes HTTP, données formulaire, réponses d'API tierce, contenu de fichier. On valide côté serveur. Le client ne contrôle rien de critique. +**Base de données** : uniquement des requêtes paramétrées — jamais de SQL construit par concaténation de chaînes. -### Base de données +**Secrets** : aucun token, clé API ou mot de passe dans le code. Tout passe par des variables d'environnement, jamais commitées. -On n'écrit jamais de SQL par concaténation de chaînes. Uniquement des requêtes paramétrées : - -```ts -// ✓ -await pool.query('SELECT * FROM users WHERE id = $1', [userId]) - -// ✗ -await pool.query(`SELECT * FROM users WHERE id = '${userId}'`) -``` - -### Secrets - -Aucun token, clé API ou mot de passe dans le code. Tout passe par des variables d'environnement. Les clés Stripe, les tokens Resend et les credentials PostgreSQL ne sont jamais commités. - -```bash -# .env.local — jamais dans git -DATABASE_URL=... -STRIPE_SECRET_KEY=... -RESEND_API_KEY=... -``` - -### Erreurs exposées - -Les messages d'erreur retournés à l'utilisateur ne contiennent pas de détails internes : pas de stack trace, pas de nom de table, pas de requête SQL. On log côté serveur, on renvoie un message générique côté client. \ No newline at end of file +**Erreurs exposées** : pas de stack trace, nom de table ou requête SQL retournés au client. On log côté serveur, on renvoie un message générique côté client.