refactor: remove modules system from core package
- Remove all module-related entry points from package.json exports - Remove module source files from tsup build configuration - Clean up external dependencies related to modules - Update DEV.md to reflect modules removal from architecture - Clarify package description to specify Next.js CMS
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ Ce document couvre les conventions de code, les règles de sécurité et la proc
|
||||
|
||||
Pour les conventions de rédaction : [LANGUE.md](./dev/LANGUE.md) et [REDACTION.md](./dev/REDACTION.md).
|
||||
|
||||
Pour l'architecture partagée (modules, composants, icônes) : [ARCHITECTURE.md](./dev/ARCHITECTURE.md).
|
||||
Pour l'architecture partagée (composants, icônes) : [ARCHITECTURE.md](./dev/ARCHITECTURE.md).
|
||||
|
||||
Pour la procédure de publication du package : [PUBLICATION.md](./dev/PUBLICATION.md).
|
||||
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
# 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: '<p>ok</p>' });
|
||||
|
||||
// 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 (
|
||||
<html>
|
||||
<body>
|
||||
<ZenProvider modules={zenConfig.modules}>
|
||||
{children}
|
||||
</ZenProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Activer le module via variable d'environnement
|
||||
|
||||
```bash
|
||||
# .env.local
|
||||
ZEN_MODULE_INVOICE=true
|
||||
```
|
||||
|
||||
La convention est la même que pour les modules internes : tirets en underscores, tout en majuscules.
|
||||
|
||||
---
|
||||
|
||||
## Initialiser la base de données
|
||||
|
||||
Le module déclare ses tables dans `db.createTables`. Deux façons de les créer.
|
||||
|
||||
**Au démarrage du serveur** (si `skipDb: false`) :
|
||||
|
||||
```js
|
||||
await initializeZen({ modules: zenConfig.modules, skipDb: false });
|
||||
```
|
||||
|
||||
**Via la CLI** :
|
||||
|
||||
```bash
|
||||
npx zen-db init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vérification rapide
|
||||
|
||||
1. Démarrer avec `ZEN_MODULE_INVOICE=true`.
|
||||
2. Ouvrir `/admin`. La section "Facturation" doit apparaître dans la navigation.
|
||||
3. Naviguer vers `/admin/invoice/list`. La page du module doit se charger.
|
||||
4. Appeler `getModuleActions('invoice')` côté serveur. Les actions du module doivent être retournées.
|
||||
@@ -1,218 +0,0 @@
|
||||
# Créer un module interne
|
||||
|
||||
Un module interne vit dans `src/modules/` et fait partie du package `@zen/core`. Il a accès direct aux services du CMS sans passer par un contexte injecté.
|
||||
|
||||
---
|
||||
|
||||
## Structure d'un module
|
||||
|
||||
```
|
||||
src/modules/mon-module/
|
||||
├── module.config.js # Obligatoire — navigation, pages, routes, cron, etc.
|
||||
├── db.js # Optionnel — createTables() et dropTables()
|
||||
├── api.js # Optionnel — routes REST
|
||||
├── cron.config.js # Optionnel — tâches planifiées
|
||||
├── crud.js # Optionnel — accès aux données
|
||||
├── admin/ # Composants React pour l'admin
|
||||
└── .env.example # Variables d'environnement requises
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## module.config.js
|
||||
|
||||
C'est la source de vérité du module. On utilise `defineModule()` pour déclarer la configuration.
|
||||
|
||||
```js
|
||||
import { lazy } from 'react';
|
||||
import { defineModule } from '../../core/modules/defineModule.js';
|
||||
|
||||
const ListPage = lazy(() => import('./admin/ListPage.js'));
|
||||
const CreatePage = lazy(() => import('./admin/CreatePage.js'));
|
||||
const EditPage = lazy(() => import('./admin/EditPage.js'));
|
||||
|
||||
export default defineModule({
|
||||
name: 'mon-module',
|
||||
displayName: 'Mon module',
|
||||
version: '1.0.0',
|
||||
description: 'Description courte.',
|
||||
|
||||
// Modules dont celui-ci dépend (vérification au démarrage)
|
||||
dependencies: [],
|
||||
|
||||
// Variables d'environnement que ce module lit
|
||||
envVars: ['ZEN_MON_MODULE_OPTION'],
|
||||
|
||||
// Navigation admin — un ou plusieurs objets de section
|
||||
navigation: [
|
||||
{
|
||||
id: 'mon-module',
|
||||
title: 'Mon module',
|
||||
icon: 'SomeIcon',
|
||||
items: [
|
||||
{ name: 'Liste', href: '/admin/mon-module/list', icon: 'SomeIcon' },
|
||||
{ name: 'Nouveau', href: '/admin/mon-module/new', icon: 'AddIcon' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
// Pages admin — chemin exact vers composant lazy
|
||||
adminPages: {
|
||||
'/admin/mon-module/list': ListPage,
|
||||
'/admin/mon-module/new': CreatePage,
|
||||
'/admin/mon-module/edit': EditPage,
|
||||
},
|
||||
|
||||
// Résolveur pour les routes dynamiques (non connues à la compilation)
|
||||
// Retourner null si le chemin ne correspond pas
|
||||
pageResolver(path) {
|
||||
const parts = path.split('/').filter(Boolean);
|
||||
if (parts[0] !== 'admin' || parts[1] !== 'mon-module') return null;
|
||||
if (parts[2] === 'list') return ListPage;
|
||||
if (parts[2] === 'new') return CreatePage;
|
||||
if (parts[2] === 'edit') return EditPage;
|
||||
return null;
|
||||
},
|
||||
|
||||
// Pages publiques accessibles sans authentification (/zen/mon-module/...)
|
||||
publicPages: {},
|
||||
publicRoutes: [],
|
||||
|
||||
// Widgets affichés sur le dashboard admin
|
||||
dashboardWidgets: [],
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## db.js
|
||||
|
||||
Si le module crée des tables, on exporte `createTables` et `dropTables`.
|
||||
|
||||
```js
|
||||
import { query } from '../../core/database/index.js';
|
||||
import { tableExists } from '../../core/database/helpers.js';
|
||||
|
||||
export async function createTables() {
|
||||
const created = [];
|
||||
const skipped = [];
|
||||
|
||||
if (!(await tableExists('zen_mon_module'))) {
|
||||
await query(`
|
||||
CREATE TABLE zen_mon_module (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
titre TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
created.push('zen_mon_module');
|
||||
} else {
|
||||
skipped.push('zen_mon_module');
|
||||
}
|
||||
|
||||
return { created, skipped };
|
||||
}
|
||||
|
||||
export async function dropTables() {
|
||||
await query('DROP TABLE IF EXISTS zen_mon_module CASCADE');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## api.js
|
||||
|
||||
Les routes API sont montées automatiquement sous `/api/zen/mon-module/...`.
|
||||
|
||||
```js
|
||||
async function handleList(request) {
|
||||
// ...
|
||||
return Response.json({ items });
|
||||
}
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
{ path: '/admin/mon-module/list', method: 'GET', handler: handleList, auth: 'admin' },
|
||||
{ path: '/mon-module/list', method: 'GET', handler: handleList, auth: 'public' },
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Valeurs acceptées pour `auth` : `'admin'` (JWT admin requis), `'user'` (JWT utilisateur requis), `'public'` (aucune auth).
|
||||
|
||||
---
|
||||
|
||||
## cron.config.js
|
||||
|
||||
```js
|
||||
import { maFonction } from './crud.js';
|
||||
|
||||
export default {
|
||||
jobs: [
|
||||
{
|
||||
name: 'mon-module:nettoyage',
|
||||
schedule: '0 3 * * *', // Tous les jours à 3h
|
||||
handler: maFonction,
|
||||
timezone: 'America/Toronto',
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enregistrement — 2 étapes
|
||||
|
||||
Après avoir créé les fichiers, on enregistre le module dans deux endroits.
|
||||
|
||||
### 1. `src/modules/modules.registry.js`
|
||||
|
||||
Ajouter le nom du module à `AVAILABLE_MODULES` :
|
||||
|
||||
```js
|
||||
export const AVAILABLE_MODULES = [
|
||||
'posts',
|
||||
'mon-module', // ajout
|
||||
];
|
||||
```
|
||||
|
||||
### 2. `src/modules/modules.pages.js`
|
||||
|
||||
Importer la config et l'ajouter à `MODULE_CONFIGS` :
|
||||
|
||||
```js
|
||||
import postsConfig from './posts/module.config.js';
|
||||
import monModuleConfig from './mon-module/module.config.js'; // ajout
|
||||
|
||||
const MODULE_CONFIGS = {
|
||||
posts: postsConfig,
|
||||
'mon-module': monModuleConfig, // ajout
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Activation
|
||||
|
||||
Le module est ignoré au démarrage tant que sa variable d'environnement n'est pas définie :
|
||||
|
||||
```bash
|
||||
# .env.local
|
||||
ZEN_MODULE_MON_MODULE=true
|
||||
```
|
||||
|
||||
La règle de conversion : tirets et espaces deviennent des underscores, tout en majuscules.
|
||||
|
||||
```
|
||||
mon-module → ZEN_MODULE_MON_MODULE
|
||||
post-types → ZEN_MODULE_POST_TYPES
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vérification rapide
|
||||
|
||||
1. Démarrer le serveur avec `ZEN_MODULE_MON_MODULE=true`.
|
||||
2. Ouvrir `/admin`. La section de navigation du module doit apparaître.
|
||||
3. Naviguer vers `/admin/mon-module/list`. La page doit se charger.
|
||||
4. Lancer `npx zen-db init`. La table `zen_mon_module` doit être créée.
|
||||
Reference in New Issue
Block a user