feat(modules): add external module system with auto-discovery and public pages support
- add `src/core/modules/` with registry, discovery (server), and public index - add `src/core/public-pages/` with registry, server component, and public index - add `src/core/users/permissions-registry.js` for runtime permission registration - expose `./modules`, `./public-pages`, and `./public-pages/server` package exports - rename `registerFeatureRoutes` to `registerApiRoutes` with backward-compatible alias - extend `seedDefaultRolesAndPermissions` to include module-registered permissions - update `initializeZen` and shared init to wire module discovery and registration - add `docs/MODULES.md` documenting the `@zen/module-*` authoring contract - update `docs/DEV.md` with references to module system docs
This commit is contained in:
+59
-7
@@ -1,25 +1,66 @@
|
||||
/**
|
||||
* Core Feature Database Initialization (CLI)
|
||||
* Database initialization for features and modules.
|
||||
*
|
||||
* Initialise et supprime les tables des features core. La liste est aujourd'hui
|
||||
* limitée à auth — le seul feature avec un schéma. Ajouter ici si un autre
|
||||
* feature gagne un db.js avec createTables()/dropTables().
|
||||
* - Features core : auth (et tout futur core ayant un db.js).
|
||||
* - Modules externes : découverts via discoverModules() ; chaque module
|
||||
* exporte ses propres createTables/dropTables.
|
||||
*
|
||||
* Les permissions ajoutées par les modules doivent être enregistrées AVANT
|
||||
* le seed de la BD pour qu'elles soient persistées et auto-attribuées au
|
||||
* rôle admin. C'est pour cela qu'on appelle register() de chaque module
|
||||
* avant initFeatures().
|
||||
*/
|
||||
|
||||
import { createTables as authCreate, dropTables as authDrop } from './auth/db.js';
|
||||
import { done, fail, info, step } from '@zen/core/shared/logger';
|
||||
import { discoverModules, validateModuleEnvVars } from '../core/modules/discover.server.js';
|
||||
import { getRegisteredModules } from '../core/modules/registry.js';
|
||||
import { registerPermissions } from '../core/users/permissions-registry.js';
|
||||
|
||||
const FEATURES = [
|
||||
const CORE_FEATURES = [
|
||||
{ name: 'auth', createTables: authCreate, dropTables: authDrop },
|
||||
];
|
||||
|
||||
async function loadModules() {
|
||||
await discoverModules();
|
||||
const modules = getRegisteredModules();
|
||||
validateModuleEnvVars(modules);
|
||||
|
||||
// Enregistre les permissions du module et exécute son register() pour que
|
||||
// tous les hooks runtime soient en place avant le seed.
|
||||
for (const mod of modules) {
|
||||
if (Array.isArray(mod.manifest?.permissions)) {
|
||||
registerPermissions(mod.manifest.permissions);
|
||||
}
|
||||
if (typeof mod.register === 'function') {
|
||||
try {
|
||||
await mod.register();
|
||||
} catch (error) {
|
||||
fail(`zen-modules: ${mod.manifest.name} register() threw — ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
export async function initFeatures() {
|
||||
const created = [];
|
||||
const skipped = [];
|
||||
|
||||
step('Initializing feature databases...');
|
||||
|
||||
for (const { name, createTables } of FEATURES) {
|
||||
// Charger les modules d'abord pour que leurs permissions soient connues
|
||||
// au moment du seed (et donc auto-attribuées au rôle admin).
|
||||
const modules = await loadModules();
|
||||
|
||||
const targets = [
|
||||
...CORE_FEATURES,
|
||||
...modules
|
||||
.filter(m => typeof m.createTables === 'function')
|
||||
.map(m => ({ name: m.manifest.name, createTables: m.createTables, dropTables: m.dropTables })),
|
||||
];
|
||||
|
||||
for (const { name, createTables } of targets) {
|
||||
try {
|
||||
step(`Initializing ${name}...`);
|
||||
if (typeof createTables !== 'function') {
|
||||
@@ -40,7 +81,18 @@ export async function initFeatures() {
|
||||
}
|
||||
|
||||
export async function dropFeatures() {
|
||||
for (const { name, dropTables } of [...FEATURES].reverse()) {
|
||||
const modules = await loadModules();
|
||||
|
||||
// Ordre de création : core, puis modules. Drop = ordre inverse pour que
|
||||
// les tables modules (qui peuvent avoir des FK vers core) tombent d'abord.
|
||||
const targets = [
|
||||
...CORE_FEATURES,
|
||||
...modules
|
||||
.filter(m => typeof m.dropTables === 'function')
|
||||
.map(m => ({ name: m.manifest.name, dropTables: m.dropTables })),
|
||||
];
|
||||
|
||||
for (const { name, dropTables } of [...targets].reverse()) {
|
||||
try {
|
||||
if (typeof dropTables !== 'function') {
|
||||
info(`${name} has no dropTables function`);
|
||||
|
||||
Reference in New Issue
Block a user