- add optional `permission` field to nav items in registry - filter nav items by user permissions in `buildNavigationSections` - auto-hide sections when all their items are filtered out - fetch user permissions in `AdminLayout.server.js` and pass to navigation builder - update docs and README to document `permission` param and new signature
6.2 KiB
Guide de développement
Ce document couvre les conventions de code, les règles de sécurité et la procédure de publication du package @zen/core.
Pour les conventions de rédaction : LANGUE.md et REDACTION.md.
Pour les décisions de design et l'identité visuelle : DESIGN.md.
Pour l'architecture partagée (composants, icônes) : ARCHITECTURE.md.
Pour la procédure de publication du package : PUBLICATION.md.
Pour les conventions de commit : COMMITS.md.
Contexte projet : les utilisateurs finaux créent leur projet via
npx @zen/start, qui génère automatiquement une structure Next.js avec la plateforme déjà intégré. Lors d'une assistance ou d'une revue, partez du principe que le projet cible est déjà structuré de cette façon.
Standards de code
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.
Le flux de contrôle se lit de haut en bas. Pas de récursion non bornée, pas de callbacks imbriqués à plus de deux niveaux. Quelqu'un qui lit le code pour la première fois doit pouvoir suivre l'exécution sans se perdre.
Les données entrantes sont suspectes. On valide en entrée de fonction. On ne suppose pas que l'appelant a fait le travail.
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 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.
Conventions d'arborescence
Une feature (src/features/<nom>/ ou src/core/<nom>/) suit la règle flat + un barrel :
un index.js qui ré-exporte, des fichiers plats côte à côte pour l'implémentation. Pas de sous-dossier lib/, middleware/, actions/ quand il ne contient qu'un ou deux fichiers — remonter directement au niveau du dossier feature.
Les sous-dossiers sont autorisés uniquement quand ils contiennent plusieurs fichiers du même rôle : components/, pages/, templates/, widgets/.
Suffixes de runtime
Tout fichier épinglé à une frontière Next.js porte le suffixe dans son nom :
.server.js→ code serveur strict (peut importerpg,fs, etc.).client.js→ débute par'use client'- 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é. 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
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 desrc/. - 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 deexportset restent toujours dans la listeexternal. - Les fichiers
.jset.jsxsont tous traités comme du JSX via esbuild (loader: { '.js': 'jsx' },jsx: 'automatic') — pas besoin d'extension.jsxpour écrire du JSX.
Étendre l'admin
L'admin utilise un registre runtime pour permettre aux projets consommateurs d'ajouter des widgets, des entrées de navigation, et des pages sans modifier le core.
// app/zen.extensions.js — projet consommateur
import {
registerWidget,
registerWidgetFetcher,
registerNavItem,
registerNavSection,
registerPage,
} from '@zen/core/features/admin';
import OrdersWidget from './admin/OrdersWidget';
import OrdersPage from './admin/OrdersPage';
import { countOrders } from './admin/orders.server';
registerWidgetFetcher('orders', async () => ({ total: await countOrders() }));
registerWidget({ id: 'orders', Component: OrdersWidget, order: 20 });
registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 });
registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce', permission: 'orders.view' });
registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes' });
// app/layout.js — un seul import suffit ; les side effects enregistrent tout.
import './zen.extensions';
Sécurité
Données entrantes : toute donnée externe est considérée malveillante par défaut. On valide côté serveur uniquement.
Base de données : uniquement des requêtes paramétrées — jamais de SQL construit par concaténation de chaînes.
Secrets : aucun token, clé API ou mot de passe dans le code. Tout passe par des variables d'environnement, jamais commitées.
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.