# Créer un module externe Un module externe est un package npm indépendant qui s'intègre dans une app qui utilise `@zen/core`. Il n'a pas besoin de modifier le code source du CMS. --- ## Convention de nommage ``` @scope/zen-nom-du-module ``` Exemples : `@zen/core-invoice`, `@zen/core-nuage`. --- ## Structure du package ``` zen-invoice/ ├── index.js # Point d'entrée — exporte defineModule() ├── admin/ # Composants React pour l'admin │ ├── InvoiceListPage.js │ ├── InvoiceCreatePage.js │ └── InvoiceEditPage.js ├── db.js # createTables() et dropTables() ├── actions.js # Server actions pour pages publiques ├── metadata.js # Générateurs de métadonnées SEO ├── package.json └── .env.example ``` --- ## index.js On utilise `defineModule()` importé depuis `@zen/core/modules/define`. ```js import { lazy } from 'react'; import { defineModule } from '@zen/core/modules/define'; import { createTables, dropTables } from './db.js'; import { getInvoiceByToken } from './actions.js'; import { generatePaymentMetadata } from './metadata.js'; const InvoiceListPage = lazy(() => import('./admin/InvoiceListPage.js')); const InvoiceCreatePage = lazy(() => import('./admin/InvoiceCreatePage.js')); const InvoiceEditPage = lazy(() => import('./admin/InvoiceEditPage.js')); export default defineModule({ name: 'invoice', displayName: 'Facturation', version: '1.0.0', description: 'Gestion des factures et paiements.', dependencies: [], envVars: ['STRIPE_SECRET_KEY', 'ZEN_INVOICE_TAX_RATE'], // Navigation dans le panneau admin navigation: [ { id: 'invoice', title: 'Facturation', icon: 'Invoice03Icon', items: [ { name: 'Factures', href: '/admin/invoice/list', icon: 'Invoice03Icon' }, { name: 'Nouvelle', href: '/admin/invoice/new', icon: 'Add01Icon' }, ], }, ], // Pages admin avec leurs composants lazy adminPages: { '/admin/invoice/list': InvoiceListPage, '/admin/invoice/new': InvoiceCreatePage, '/admin/invoice/edit': InvoiceEditPage, }, // Résolveur pour les routes dynamiques (ex: /admin/invoice/edit/123) pageResolver(path) { const parts = path.split('/').filter(Boolean); if (parts[0] !== 'admin' || parts[1] !== 'invoice') return null; if (parts[2] === 'list') return InvoiceListPage; if (parts[2] === 'new') return InvoiceCreatePage; if (parts[2] === 'edit') return InvoiceEditPage; return null; }, publicPages: {}, publicRoutes: [ { pattern: ':token', description: 'Page de paiement' }, { pattern: ':token/pdf', description: 'Télécharger la facture PDF' }, ], dashboardWidgets: [], // Base de données db: { createTables, dropTables }, // Server actions pour les pages publiques (/zen/invoice/...) actions: { getInvoiceByToken }, // Générateurs de métadonnées SEO metadata: { payment: generatePaymentMetadata, }, // Appelé une fois au démarrage du serveur // ctx donne accès aux services du CMS async setup(ctx) { const stripe = await ctx.payments.then(p => p.stripe); // Initialiser des webhooks, vérifier la config, etc. console.log('[invoice] Stripe prêt :', !!stripe); }, }); ``` --- ## L'objet ctx dans setup() `setup(ctx)` reçoit un objet qui donne accès aux services du CMS. Chaque propriété retourne une promesse vers le module correspondant. ```js async setup(ctx) { // Base de données PostgreSQL const { query, queryOne, queryAll } = await ctx.db; const rows = await query('SELECT * FROM zen_auth_users LIMIT 1'); // Envoi de courriels (Resend) const { sendEmail } = await ctx.email; await sendEmail({ to: 'test@example.com', subject: 'Test', html: '
ok
' }); // Stockage de fichiers (Cloudflare R2) const { uploadFile, deleteFile } = await ctx.storage; // Stripe const { stripe } = await ctx.payments; // Variables d'environnement const apiKey = ctx.config.get('STRIPE_SECRET_KEY'); } ``` --- ## package.json du module externe ```json { "name": "@zen/core-invoice", "version": "1.0.0", "type": "module", "main": "./index.js", "exports": { ".": "./index.js" }, "peerDependencies": { "@zen/core": ">=1.0.0", "react": ">=19.0.0" } } ``` --- ## Intégration dans l'app consommatrice ### 1. Installer le package ```bash npm install @zen/core-invoice ``` ### 2. Créer zen.config.js à la racine de l'app ```js // zen.config.js import invoiceModule from '@zen/core-invoice'; export default { modules: [invoiceModule], }; ``` ### 3. Passer la config à initializeZen() ```js // instrumentation.js import zenConfig from './zen.config.js'; export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { const { initializeZen } = await import('@zen/core'); await initializeZen(zenConfig); } } ``` ### 4. Passer les modules à ZenProvider ```jsx // app/layout.js import zenConfig from './zen.config.js'; import { ZenProvider } from '@zen/core/provider'; export default function RootLayout({ children }) { return (