feat(modules): add external module registration and defineModule support

- Add `./modules/define` export path pointing to `defineModule.js`
- Implement `registerExternalModules()` to handle modules passed via `zen.config.js`, with env var gating (`ZEN_MODULE_<NAME>=true`)
- Extract `buildAdminConfig()` helper to consolidate admin navigation/page config building
- Refactor `loadModuleConfig()` to use `buildAdminConfig()` and simplify public routes check
- Improve `initializeModuleTables()` to gracefully skip modules without `db.js` instead of erroring
- Update module discovery JSDoc to reflect external module registration support
This commit is contained in:
2026-04-12 13:39:56 -04:00
parent 4983a24325
commit 99a56d2c39
13 changed files with 871 additions and 146 deletions
+32 -18
View File
@@ -3,7 +3,7 @@
* Initialize all ZEN services and modules using dynamic module discovery
*/
import { discoverModules, startModuleCronJobs, stopModuleCronJobs } from '../../core/modules/index.js';
import { discoverModules, registerExternalModules, startModuleCronJobs, stopModuleCronJobs } from '../../core/modules/index.js';
// Use globalThis to persist initialization flag across module reloads
const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__');
@@ -16,26 +16,32 @@ const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__');
* Alternative: Call this function manually in your root layout
*
* @example
* // instrumentation.js (Recommended)
* // instrumentation.js (Recommended) — internal modules only
* export async function register() {
* if (process.env.NEXT_RUNTIME === 'nodejs') {
* const { initializeZen } = await import('@hykocx/zen');
* await initializeZen();
* }
* }
*
*
* @example
* // app/layout.js (Alternative)
* import { initializeZen } from '@hykocx/zen';
* initializeZen();
*
* @param {Object} options - Initialization options
* @param {boolean} options.skipCron - Skip cron job initialization
* @param {boolean} options.skipDb - Skip database initialization
* // instrumentation.js — with external modules from zen.config.js
* import zenConfig from './zen.config.js';
* export async function register() {
* if (process.env.NEXT_RUNTIME === 'nodejs') {
* const { initializeZen } = await import('@hykocx/zen');
* await initializeZen(zenConfig);
* }
* }
*
* @param {Object} config - Configuration object
* @param {Array} config.modules - External module configs (from zen.config.js)
* @param {boolean} config.skipCron - Skip cron job initialization
* @param {boolean} config.skipDb - Skip database initialization
* @returns {Promise<Object>} Initialization result
*/
export async function initializeZen(options = {}) {
const { skipCron = false, skipDb = true } = options;
export async function initializeZen(config = {}) {
const { modules: externalModules = [], skipCron = false, skipDb = true } = config;
// Only run on server-side
if (typeof window !== 'undefined') {
@@ -57,21 +63,29 @@ export async function initializeZen(options = {}) {
};
try {
// Step 1: Discover and register all enabled modules
// This reads from modules.registry.js and loads each module's config files
// Step 1: Discover and register internal modules (from modules.registry.js)
result.discovery = await discoverModules();
const enabledCount = result.discovery.enabled?.length || 0;
const skippedCount = result.discovery.skipped?.length || 0;
if (enabledCount > 0) {
console.log(`✓ ZEN: Discovered ${enabledCount} enabled module(s): ${result.discovery.enabled.join(', ')}`);
console.log(`✓ ZEN: Discovered ${enabledCount} internal module(s): ${result.discovery.enabled.join(', ')}`);
}
if (skippedCount > 0) {
console.log(`⚠ ZEN: Skipped ${skippedCount} disabled module(s): ${result.discovery.skipped.join(', ')}`);
}
// Step 2: Register external modules from zen.config.js (if any)
if (externalModules.length > 0) {
result.external = await registerExternalModules(externalModules);
if (result.external.registered.length > 0) {
console.log(`✓ ZEN: Registered ${result.external.registered.length} external module(s): ${result.external.registered.join(', ')}`);
}
}
// Step 2: Start cron jobs for all enabled modules
// Step 3: Start cron jobs for all enabled modules (internal + external)
if (!skipCron) {
result.cron = await startModuleCronJobs();