diff --git a/src/cli/database.js b/src/cli/database.js index 2a9a1fe..391c356 100644 --- a/src/cli/database.js +++ b/src/cli/database.js @@ -18,6 +18,7 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'; import { initDatabase, dropAuthTables, testConnection, closePool } from '../core/database/index.js'; import readline from 'readline'; +import { step, done, warn, fail } from '../shared/lib/logger.js'; async function runCLI() { const command = process.argv[2]; @@ -44,25 +45,25 @@ Example: try { switch (command) { case 'init': - console.log('๐Ÿ”ง Initializing database...\n'); + step('Initializing database...'); const result = await initDatabase(); - console.log(`\nโœ… Success! Created ${result.created.length} tables, skipped ${result.skipped.length} existing tables.`); + done(`Created ${result.created.length} tables, skipped ${result.skipped.length} existing tables`); break; case 'test': - console.log('๐Ÿ”Œ Testing database connection...\n'); + step('Testing database connection...'); const isConnected = await testConnection(); if (isConnected) { - console.log('โœ… Database connection successful!'); + done('Database connection successful'); } else { - console.log('โŒ Database connection failed!'); + fail('Database connection failed'); process.exit(1); } break; case 'drop': - console.log('โš ๏ธ WARNING: This will delete all authentication tables!\n'); - console.log('Type "yes" to confirm or Ctrl+C to cancel...'); + warn('This will delete all authentication tables!'); + process.stdout.write(' Type "yes" to confirm or Ctrl+C to cancel...\n'); const rl = readline.createInterface({ input: process.stdin, @@ -72,9 +73,9 @@ Example: rl.question('Confirm (yes/no): ', async (answer) => { if (answer.toLowerCase() === 'yes') { await dropAuthTables(); - console.log('โœ… Tables dropped successfully.'); + done('Tables dropped successfully'); } else { - console.log('โŒ Operation cancelled.'); + warn('Operation cancelled'); } rl.close(); process.exit(0); @@ -97,8 +98,8 @@ Usage: break; default: - console.log(`โŒ Unknown command: ${command}`); - console.log('Run "npx zen-db help" for usage information.'); + fail(`Unknown command: ${command}`); + process.stdout.write(' Run "npx zen-db help" for usage information.\n'); process.exit(1); } @@ -107,7 +108,7 @@ Usage: process.exit(0); } catch (error) { - console.error('โŒ Error:', error.message); + fail(`Error: ${error.message}`); process.exit(1); } } diff --git a/src/core/api/handlers/storage.js b/src/core/api/handlers/storage.js index 5dfa236..21b4a31 100644 --- a/src/core/api/handlers/storage.js +++ b/src/core/api/handlers/storage.js @@ -8,6 +8,7 @@ import { cookies } from 'next/headers'; import { getSessionCookieName } from '../../../shared/lib/appConfig.js'; import { getAllStoragePublicPrefixes } from '@zen/core/modules/storage'; import { getFile } from '@zen/core/storage'; +import { fail } from '../../../shared/lib/logger.js'; // Get cookie name from environment or use default const COOKIE_NAME = getSessionCookieName(); @@ -151,7 +152,7 @@ export async function handleGetFile(_request, fileKey) { }; } catch (error) { // Log full error server-side; never surface internal details to the client. - console.error('[ZEN STORAGE] Error serving file:', error); + fail(`Storage: error serving file: ${error.message}`); return { error: 'Internal Server Error', message: 'Failed to retrieve file' diff --git a/src/core/api/handlers/users.js b/src/core/api/handlers/users.js index 1b2eedf..05e2e47 100644 --- a/src/core/api/handlers/users.js +++ b/src/core/api/handlers/users.js @@ -9,6 +9,7 @@ import { query, updateById } from '@zen/core/database'; import { getSessionCookieName } from '../../../shared/lib/appConfig.js'; import { updateUser } from '../../../features/auth/lib/auth.js'; import { uploadImage, deleteFile, generateUniqueFilename, generateUserFilePath, getFileExtension, FILE_TYPE_PRESETS, FILE_SIZE_LIMITS, validateUpload } from '@zen/core/storage'; +import { fail, info } from '../../../shared/lib/logger.js'; // Get cookie name from environment or use default const COOKIE_NAME = getSessionCookieName(); @@ -36,7 +37,7 @@ const ALLOWED_IMAGE_MIME_TYPES = new Set([ * @param {string} fallback - The safe message to surface to the client */ function logAndObscureError(error, fallback) { - console.error('[ZEN] Internal handler error:', error); + fail(`Internal handler error: ${error.message}`); return fallback; } @@ -468,7 +469,7 @@ export async function handleUploadProfilePicture(request) { try { await deleteFile(key); } catch (rollbackError) { - console.error('[ZEN] Rollback delete of newly uploaded object failed:', rollbackError); + fail(`Rollback delete of newly uploaded object failed: ${rollbackError.message}`); } throw dbError; } @@ -477,10 +478,10 @@ export async function handleUploadProfilePicture(request) { if (oldImageKey) { try { await deleteFile(oldImageKey); - console.log(`[ZEN] Deleted old profile picture: ${oldImageKey}`); + info(`Deleted old profile picture: ${oldImageKey}`); } catch (deleteError) { // Non-fatal: log for operator cleanup; the DB is consistent. - console.error('[ZEN] Failed to delete old profile picture (orphaned object):', deleteError); + fail(`Failed to delete old profile picture (orphaned object): ${deleteError.message}`); } } @@ -568,10 +569,10 @@ export async function handleDeleteProfilePicture(request) { if (imageKey) { try { await deleteFile(imageKey); - console.log(`[ZEN] Deleted profile picture: ${imageKey}`); + info(`Deleted profile picture: ${imageKey}`); } catch (deleteError) { // Log error but don't fail the update - console.error('[ZEN] Failed to delete profile picture from storage:', deleteError); + fail(`Failed to delete profile picture from storage: ${deleteError.message}`); } } diff --git a/src/core/api/nx-route.js b/src/core/api/nx-route.js index 4c39f21..89fad44 100644 --- a/src/core/api/nx-route.js +++ b/src/core/api/nx-route.js @@ -7,6 +7,7 @@ import { NextResponse } from 'next/server'; import { routeRequest, getStatusCode } from './router.js'; +import { fail } from '../../shared/lib/logger.js'; /** * Handle GET requests @@ -58,7 +59,7 @@ export async function GET(request, { params }) { } }); } catch (error) { - console.error('API Error:', error); + fail(`API error: ${error.message}`); return NextResponse.json( { error: 'Internal Server Error', @@ -86,7 +87,7 @@ export async function POST(request, { params }) { } }); } catch (error) { - console.error('API Error:', error); + fail(`API error: ${error.message}`); return NextResponse.json( { error: 'Internal Server Error', @@ -114,7 +115,7 @@ export async function PUT(request, { params }) { } }); } catch (error) { - console.error('API Error:', error); + fail(`API error: ${error.message}`); return NextResponse.json( { error: 'Internal Server Error', @@ -142,7 +143,7 @@ export async function DELETE(request, { params }) { } }); } catch (error) { - console.error('API Error:', error); + fail(`API error: ${error.message}`); return NextResponse.json( { error: 'Internal Server Error', @@ -170,7 +171,7 @@ export async function PATCH(request, { params }) { } }); } catch (error) { - console.error('API Error:', error); + fail(`API error: ${error.message}`); return NextResponse.json( { error: 'Internal Server Error', diff --git a/src/core/api/router.js b/src/core/api/router.js index 0bb6556..7966408 100644 --- a/src/core/api/router.js +++ b/src/core/api/router.js @@ -12,6 +12,7 @@ import { cookies } from 'next/headers'; import { getSessionCookieName } from '../../shared/lib/appConfig.js'; import { getAllApiRoutes } from '../modules/index.js'; import { checkRateLimit, getIpFromRequest, formatRetryAfter } from '../../features/auth/lib/rateLimit.js'; +import { fail } from '../../shared/lib/logger.js'; // Core handlers import { handleHealth } from './handlers/health.js'; @@ -64,7 +65,7 @@ function passesCsrfCheck(request) { const appUrl = resolveAppUrl(); if (!appUrl) { - console.error('[ZEN CSRF] No app URL configured (NEXT_PUBLIC_URL_DEV / NEXT_PUBLIC_URL) โ€” CSRF check denied.'); + fail('CSRF: no app URL configured (NEXT_PUBLIC_URL_DEV / NEXT_PUBLIC_URL) โ€” request denied'); return false; } @@ -72,7 +73,7 @@ function passesCsrfCheck(request) { try { expectedOrigin = new URL(appUrl).origin; } catch { - console.error('[ZEN CSRF] Configured app URL is not valid:', appUrl); + fail(`CSRF: configured app URL is not valid: ${appUrl}`); return false; } @@ -295,7 +296,7 @@ async function routeModuleRequest(request, path, method) { // or internal hostnames. Log the full error server-side only. const SAFE_AUTH_MESSAGES = new Set(['Unauthorized', 'Admin access required']); if (!SAFE_AUTH_MESSAGES.has(err.message)) { - console.error('[ZEN] Module route handler error:', err); + fail(`Module route handler error: ${err.message}`); } return { success: false, diff --git a/src/core/cron/index.js b/src/core/cron/index.js index d8910a9..fff5142 100644 --- a/src/core/cron/index.js +++ b/src/core/cron/index.js @@ -7,6 +7,7 @@ */ import cron from 'node-cron'; +import { done, fail, info } from '../../shared/lib/logger.js'; // Store for all scheduled cron jobs const CRON_JOBS_KEY = Symbol.for('__ZEN_CRON_JOBS__'); @@ -47,19 +48,18 @@ export function schedule(name, cronSchedule, handler, options = {}) { // Stop existing job with same name if (jobs.has(name)) { jobs.get(name).stop(); - console.log(`[Cron] Stopped existing job: ${name}`); + info(`Cron replaced: ${name}`); } const timezone = options.timezone || process.env.ZEN_TIMEZONE || 'America/Toronto'; const job = cron.schedule(cronSchedule, async () => { - console.log(`[Cron: ${name}] Running at:`, new Date().toISOString()); - + info(`Cron ${name} running at ${new Date().toISOString()}`); try { await handler(); - console.log(`[Cron: ${name}] Completed`); + info(`Cron ${name} completed`); } catch (error) { - console.error(`[Cron: ${name}] Error:`, error); + fail(`Cron ${name}: ${error.message}`); } }, { scheduled: true, @@ -68,7 +68,7 @@ export function schedule(name, cronSchedule, handler, options = {}) { }); jobs.set(name, job); - console.log(`[Cron] Scheduled job: ${name} (${cronSchedule})`); + done(`Cron scheduled: ${name} (${cronSchedule})`); return job; } @@ -84,7 +84,7 @@ export function stop(name) { if (jobs.has(name)) { jobs.get(name).stop(); jobs.delete(name); - console.log(`[Cron] Stopped job: ${name}`); + info(`Cron stopped: ${name}`); return true; } @@ -99,11 +99,11 @@ export function stopAll() { for (const [name, job] of jobs.entries()) { job.stop(); - console.log(`[Cron] Stopped job: ${name}`); + info(`Cron stopped: ${name}`); } jobs.clear(); - console.log('[Cron] All jobs stopped'); + done('All cron jobs stopped'); } /** @@ -161,7 +161,7 @@ export async function trigger(name) { throw new Error(`Cron job '${name}' not found`); } - console.log(`[Cron] Manual trigger for: ${name}`); + info(`Cron manual trigger: ${name}`); // Note: node-cron doesn't expose the handler directly, // so modules should keep their handler function accessible } diff --git a/src/core/database/db.js b/src/core/database/db.js index f775227..1ccc80d 100644 --- a/src/core/database/db.js +++ b/src/core/database/db.js @@ -5,6 +5,7 @@ import pkg from 'pg'; const { Pool } = pkg; +import { fail } from '../../shared/lib/logger.js'; let pool = null; @@ -44,7 +45,7 @@ function getPool() { // Handle pool errors pool.on('error', (err) => { - console.error('Unexpected error on idle client', err); + fail(`DB idle client error: ${err.message}`); }); } @@ -64,7 +65,7 @@ async function query(sql, params = []) { const result = await client.query(sql, params); return result; } catch (error) { - console.error('Database query error:', error); + fail(`DB query error: ${error.message}`); throw error; } } @@ -106,7 +107,7 @@ async function transaction(callback) { return result; } catch (error) { await client.query('ROLLBACK'); - console.error('Transaction error:', error); + fail(`DB transaction error: ${error.message}`); throw error; } finally { client.release(); @@ -133,7 +134,7 @@ async function testConnection() { await query('SELECT NOW()'); return true; } catch (error) { - console.error('Database connection test failed:', error); + fail(`DB connection test failed: ${error.message}`); return false; } } diff --git a/src/core/database/init.js b/src/core/database/init.js index 7b74983..348afd0 100644 --- a/src/core/database/init.js +++ b/src/core/database/init.js @@ -4,6 +4,7 @@ */ import { query } from './db.js'; +import { step, done, warn, fail, info } from '../../shared/lib/logger.js'; /** * Check if a table exists in the database @@ -103,10 +104,10 @@ async function createAuthTables() { if (!exists) { await query(table.sql); created.push(table.name); - console.log(`โœ“ Created table: ${table.name}`); + done(`Created table: ${table.name}`); } else { skipped.push(table.name); - console.log(`- Table already exists: ${table.name}`); + info(`Table already exists: ${table.name}`); } } @@ -122,7 +123,7 @@ async function createAuthTables() { * @returns {Promise} Result object with created and skipped tables */ async function initDatabase() { - console.log('Initializing Zen database...'); + step('Initializing Zen database...'); try { const authResult = await createAuthTables(); @@ -134,13 +135,10 @@ async function initDatabase() { modulesResult = await initModules(); } catch (error) { // Modules might not be available or enabled - console.log('\nNo modules to initialize or modules not available.'); + info('No modules to initialize or modules not available'); } - console.log('\nDatabase initialization completed!'); - console.log(`Auth tables created: ${authResult.created.length}`); - console.log(`Module tables created: ${modulesResult.created.length}`); - console.log(`Total tables skipped: ${authResult.skipped.length + modulesResult.skipped.length}`); + done(`DB ready โ€” auth: ${authResult.created.length} created, modules: ${modulesResult.created.length} created, ${authResult.skipped.length + modulesResult.skipped.length} skipped`); return { created: [...authResult.created, ...modulesResult.created], @@ -148,7 +146,7 @@ async function initDatabase() { success: true }; } catch (error) { - console.error('Database initialization failed:', error); + fail(`DB initialization failed: ${error.message}`); throw error; } } @@ -165,17 +163,17 @@ async function dropAuthTables() { 'zen_auth_users' ]; - console.log('WARNING: Dropping all Zen authentication tables...'); + warn('Dropping all Zen authentication tables...'); for (const tableName of tables) { const exists = await tableExists(tableName); if (exists) { await query(`DROP TABLE IF EXISTS "${tableName}" CASCADE`); - console.log(`โœ“ Dropped table: ${tableName}`); + done(`Dropped table: ${tableName}`); } } - console.log('All authentication tables dropped.'); + done('All authentication tables dropped'); } export { diff --git a/src/core/email/index.js b/src/core/email/index.js index 5721721..8e7fd71 100644 --- a/src/core/email/index.js +++ b/src/core/email/index.js @@ -4,6 +4,7 @@ */ import { Resend } from 'resend'; +import { done, fail, info } from '../../shared/lib/logger.js'; /** * Initialize Resend client @@ -74,7 +75,7 @@ async function sendEmail({ to, subject, html, text, from, fromName, replyTo, att // Resend returns { data: { id: "..." }, error: null } or { data: null, error: { message: "..." } } if (response.error) { - console.error('[ZEN EMAIL] Resend error:', response.error); + fail(`Email Resend error: ${response.error.message || response.error}`); return { success: false, data: null, @@ -83,7 +84,7 @@ async function sendEmail({ to, subject, html, text, from, fromName, replyTo, att } const emailId = response.data?.id || response.id; - console.log(`[ZEN EMAIL] Email sent to ${to} - ID: ${emailId}`); + info(`Email sent to ${to} โ€” ID: ${emailId}`); return { success: true, @@ -91,7 +92,7 @@ async function sendEmail({ to, subject, html, text, from, fromName, replyTo, att error: null }; } catch (error) { - console.error('[ZEN EMAIL] Error sending email:', error); + fail(`Email send failed: ${error.message}`); return { success: false, data: null, @@ -176,7 +177,7 @@ async function sendBatchEmails(emails) { // Handle Resend error response if (response.error) { - console.error('[ZEN EMAIL] Resend batch error:', response.error); + fail(`Email batch Resend error: ${response.error.message || response.error}`); return { success: false, data: null, @@ -184,7 +185,7 @@ async function sendBatchEmails(emails) { }; } - console.log(`[ZEN EMAIL] Batch of ${emails.length} emails sent`); + done(`Email batch of ${emails.length} sent`); return { success: true, @@ -192,7 +193,7 @@ async function sendBatchEmails(emails) { error: null }; } catch (error) { - console.error('[ZEN EMAIL] Error sending batch emails:', error); + fail(`Email batch send failed: ${error.message}`); return { success: false, data: null, diff --git a/src/core/modules/discovery.js b/src/core/modules/discovery.js index d4585f4..2a93a80 100644 --- a/src/core/modules/discovery.js +++ b/src/core/modules/discovery.js @@ -6,6 +6,7 @@ import { registerModule, clearRegistry } from './registry.js'; import { getAvailableModules } from '../../modules/modules.registry.js'; +import { step, done, warn, fail, info } from '../../shared/lib/logger.js'; /** * Check if a module is enabled via environment variable @@ -29,15 +30,15 @@ export async function discoverModules(options = {}) { const DISCOVERY_KEY = Symbol.for('__ZEN_MODULES_DISCOVERED__'); if (globalThis[DISCOVERY_KEY] && !force) { - console.log('[Module Discovery] Already discovered, skipping...'); + warn('modules already discovered, skipping'); return { alreadyDiscovered: true }; } - + if (force) { clearRegistry(); } - - console.log('[Module Discovery] Starting module discovery...'); + + step('Discovering modules...'); const discovered = []; const enabled = []; @@ -52,7 +53,6 @@ export async function discoverModules(options = {}) { if (!isEnabled) { skipped.push(moduleName); - console.log(`[Module Discovery] Skipped ${moduleName} (not enabled)`); continue; } @@ -72,18 +72,16 @@ export async function discoverModules(options = {}) { discovered.push(moduleName); enabled.push(moduleName); - console.log(`[Module Discovery] Registered ${moduleName}`); + info(`Registered ${moduleName}`); } } catch (error) { errors.push({ module: moduleName, error: error.message }); - console.error(`[Module Discovery] Error loading ${moduleName}:`, error); + fail(`Error loading ${moduleName}: ${error.message}`); } } - + globalThis[DISCOVERY_KEY] = true; - console.log(`[Module Discovery] Complete. Enabled: ${enabled.length}, Skipped: ${skipped.length}, Errors: ${errors.length}`); - return { discovered, enabled, skipped, errors }; } @@ -112,7 +110,7 @@ async function loadModuleConfig(moduleName) { : undefined, }; } catch (error) { - console.log(`[Module Discovery] No module.config.js for ${moduleName}, using defaults`); + // No module.config.js โ€” use defaults silently return { name: moduleName, displayName: moduleName.charAt(0).toUpperCase() + moduleName.slice(1), @@ -186,7 +184,6 @@ export async function registerExternalModules(modules = []) { try { if (!isModuleEnabledInEnv(moduleName)) { skipped.push(moduleName); - console.log(`[External Modules] Skipped ${moduleName} (not enabled)`); continue; } @@ -226,19 +223,13 @@ export async function registerExternalModules(modules = []) { } registered.push(moduleName); - console.log(`[External Modules] Registered ${moduleName}`); + info(`Registered external ${moduleName}`); } catch (error) { errors.push({ module: moduleName, error: error.message }); - console.error(`[External Modules] Error registering ${moduleName}:`, error); + fail(`Error registering external ${moduleName}: ${error.message}`); } } - if (registered.length > 0 || skipped.length > 0) { - console.log( - `[External Modules] Done. Registered: ${registered.length}, Skipped: ${skipped.length}, Errors: ${errors.length}` - ); - } - return { registered, skipped, errors }; } @@ -300,10 +291,7 @@ async function buildModuleContext(allowedKeys = []) { */ get: (key) => { if (!allowedSet.has(key)) { - console.error( - `[ZEN Module Security] Module attempted to read undeclared env var "${key}". ` + - 'Access denied. Declare all required env vars in the module envVars list.' - ); + fail(`[Security] Module attempted to read undeclared env var "${key}" โ€” access denied`); return undefined; } return process.env[key]; diff --git a/src/core/modules/loader.js b/src/core/modules/loader.js index 3cc15c0..b497002 100644 --- a/src/core/modules/loader.js +++ b/src/core/modules/loader.js @@ -11,6 +11,7 @@ import { getAllDatabaseSchemas, isModuleEnabled } from './registry.js'; +import { step, done, warn, fail, info } from '../../shared/lib/logger.js'; // Use globalThis to track initialization state const INIT_KEY = Symbol.for('__ZEN_MODULES_INITIALIZED__'); @@ -30,11 +31,11 @@ export async function initializeModules(options = {}) { // Prevent multiple initializations if (globalThis[INIT_KEY] && !force) { - console.log('[Module Loader] Already initialized, skipping...'); + warn('modules already initialized, skipping'); return { alreadyInitialized: true }; } - - console.log('[Module Loader] Starting module initialization...'); + + step('Initializing modules...'); const result = { discovery: null, @@ -57,10 +58,10 @@ export async function initializeModules(options = {}) { } globalThis[INIT_KEY] = true; - console.log('[Module Loader] Module initialization complete'); - + done('Modules initialized'); + } catch (error) { - console.error('[Module Loader] Initialization failed:', error); + fail(`Module initialization failed: ${error.message}`); result.error = error.message; } @@ -72,7 +73,7 @@ export async function initializeModules(options = {}) { * @returns {Promise} Database initialization result */ export async function initializeModuleDatabases() { - console.log('[Module Loader] Initializing module databases...'); + step('Initializing module databases...'); const schemas = getAllDatabaseSchemas(); const result = { @@ -93,14 +94,14 @@ export async function initializeModuleDatabases() { result.skipped.push(...initResult.skipped); } - console.log(`[Module Loader] Database initialized for ${schema.module}`); + info(`DB ready: ${schema.module}`); } } catch (error) { result.errors.push({ module: schema.module, error: error.message }); - console.error(`[Module Loader] Database init error for ${schema.module}:`, error); + fail(`DB init error for ${schema.module}: ${error.message}`); } } @@ -112,7 +113,7 @@ export async function initializeModuleDatabases() { * @returns {Promise} Cron job start result */ export async function startModuleCronJobs() { - console.log('[Module Loader] Starting module cron jobs...'); + step('Starting cron jobs...'); // Stop existing cron jobs first stopModuleCronJobs(); @@ -135,12 +136,12 @@ export async function startModuleCronJobs() { const cron = (await import('node-cron')).default; const cronJob = cron.schedule(job.schedule, async () => { - console.log(`[Cron: ${job.name}] Running at:`, new Date().toISOString()); + info(`Cron ${job.name} running at ${new Date().toISOString()}`); try { await job.handler(); - console.log(`[Cron: ${job.name}] Completed`); + info(`Cron ${job.name} completed`); } catch (error) { - console.error(`[Cron: ${job.name}] Error:`, error); + fail(`Cron ${job.name}: ${error.message}`); } }, { scheduled: true, @@ -149,8 +150,7 @@ export async function startModuleCronJobs() { globalThis[CRON_JOBS_KEY].set(job.name, cronJob); result.started.push(job.name); - - console.log(`[Module Loader] Started cron job: ${job.name} (${job.schedule})`); + info(`Cron ready: ${job.name} (${job.schedule})`); } } catch (error) { result.errors.push({ @@ -158,7 +158,7 @@ export async function startModuleCronJobs() { module: job.module, error: error.message }); - console.error(`[Module Loader] Cron job error for ${job.name}:`, error); + fail(`Cron error for ${job.name}: ${error.message}`); } } @@ -173,9 +173,9 @@ export function stopModuleCronJobs() { for (const [name, job] of globalThis[CRON_JOBS_KEY].entries()) { try { job.stop(); - console.log(`[Module Loader] Stopped cron job: ${name}`); + info(`Cron stopped: ${name}`); } catch (error) { - console.error(`[Module Loader] Error stopping cron job ${name}:`, error); + fail(`Error stopping cron ${name}: ${error.message}`); } } globalThis[CRON_JOBS_KEY].clear(); diff --git a/src/core/storage/index.js b/src/core/storage/index.js index 1bc2721..25fa1c6 100644 --- a/src/core/storage/index.js +++ b/src/core/storage/index.js @@ -5,6 +5,7 @@ */ import { createHmac, createHash } from 'crypto'; +import { fail, warn, info } from '../../shared/lib/logger.js'; // โ”€โ”€โ”€ AWS Signature V4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -288,7 +289,7 @@ async function uploadFile({ key, body, contentType, metadata = {}, cacheControl return { success: true, data: { key, bucket: config.bucket, contentType }, error: null }; } catch (error) { - console.error('[ZEN STORAGE] Error uploading file:', error); + fail(`Storage upload failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -327,7 +328,7 @@ async function deleteFile(key) { return { success: true, data: { key }, error: null }; } catch (error) { - console.error('[ZEN STORAGE] Error deleting file:', error); + fail(`Storage delete failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -378,7 +379,7 @@ async function deleteFiles(keys) { return { success: true, data: { deleted, errors }, error: null }; } catch (error) { - console.error('[ZEN STORAGE] Error deleting files:', error); + fail(`Storage batch delete failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -419,7 +420,7 @@ async function getFile(key) { error: null, }; } catch (error) { - console.error('[ZEN STORAGE] Error getting file:', error); + fail(`Storage get file failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -457,7 +458,7 @@ async function getFileMetadata(key) { error: null, }; } catch (error) { - console.error('[ZEN STORAGE] Error getting file metadata:', error); + fail(`Storage get metadata failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -520,7 +521,7 @@ async function listFiles({ prefix = '', maxKeys = 1000, continuationToken } = {} error: null, }; } catch (error) { - console.error('[ZEN STORAGE] Error listing files:', error); + fail(`Storage list files failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -550,7 +551,7 @@ async function getPresignedUrl({ key, expiresIn = 3600, operation = 'get' }) { return { success: true, data: { key, url, expiresIn: validExpiresIn, operation }, error: null }; } catch (error) { - console.error('[ZEN STORAGE] Error generating presigned URL:', error); + fail(`Storage presigned URL failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -575,12 +576,12 @@ async function copyFile({ sourceKey, destinationKey }) { }); if (uploadResult.success) { - console.log(`[ZEN STORAGE] File copied from ${sourceKey} to ${destinationKey}`); + info(`Storage: copied ${sourceKey} โ†’ ${destinationKey}`); } return uploadResult; } catch (error) { - console.error('[ZEN STORAGE] Error copying file:', error); + fail(`Storage copy failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } @@ -622,16 +623,14 @@ async function moveFile({ sourceKey, destinationKey }) { const deleteResult = await deleteFile(sourceKey); if (!deleteResult.success) { - console.warn( - `[ZEN STORAGE] File copied to ${destinationKey} but failed to delete source ${sourceKey}` - ); + warn(`Storage: copied to ${destinationKey} but failed to delete source ${sourceKey}`); } else { - console.log(`[ZEN STORAGE] File moved from ${sourceKey} to ${destinationKey}`); + info(`Storage: moved ${sourceKey} โ†’ ${destinationKey}`); } return copyResult; } catch (error) { - console.error('[ZEN STORAGE] Error moving file:', error); + fail(`Storage move failed: ${error.message}`); return { success: false, data: null, error: error.message }; } } diff --git a/src/core/storage/utils.js b/src/core/storage/utils.js index c43dece..6bcc3af 100644 --- a/src/core/storage/utils.js +++ b/src/core/storage/utils.js @@ -4,6 +4,7 @@ */ import crypto from 'crypto'; +import { warn } from '../../shared/lib/logger.js'; /** * Generate a unique filename with timestamp and random hash @@ -205,11 +206,7 @@ export async function validateImageDimensions(buffer, constraints = {}) { // Returning valid=false with a clear diagnostic forces callers to either // install 'sharp' (the recommended path) or explicitly handle the // unvalidated case themselves. Never silently approve what cannot be checked. - console.warn( - '[ZEN STORAGE] validateImageDimensions: image dimension enforcement is not ' + - 'available. Install the "sharp" package and implement pixel-level validation ' + - 'before enabling uploads that depend on dimension constraints.' - ); + warn('Storage: validateImageDimensions โ€” dimension enforcement unavailable. Install "sharp" to enable pixel-level validation.'); return { valid: false, width: null, diff --git a/src/features/admin/actions/statsActions.js b/src/features/admin/actions/statsActions.js index 2fc9332..7d8fceb 100644 --- a/src/features/admin/actions/statsActions.js +++ b/src/features/admin/actions/statsActions.js @@ -38,6 +38,7 @@ 'use server'; import { query } from '@zen/core/database'; +import { fail } from '../../../shared/lib/logger.js'; /** * Get total number of users @@ -50,7 +51,7 @@ async function getTotalUsersCount() { ); return parseInt(result.rows[0].count) || 0; } catch (error) { - console.error('Error getting users count:', error); + fail(`Error getting users count: ${error.message}`); return 0; } } @@ -70,7 +71,7 @@ export async function getDashboardStats() { } }; } catch (error) { - console.error('Error getting dashboard stats:', error); + fail(`Error getting dashboard stats: ${error.message}`); return { success: false, error: error.message || 'Failed to get dashboard statistics' diff --git a/src/features/auth/actions/authActions.js b/src/features/auth/actions/authActions.js index 2e772a2..1d464a3 100644 --- a/src/features/auth/actions/authActions.js +++ b/src/features/auth/actions/authActions.js @@ -8,6 +8,7 @@ import { register, login, requestPasswordReset, resetPassword, verifyUserEmail } from '../lib/auth.js'; import { validateSession, deleteSession } from '../lib/session.js'; import { verifyEmailToken, verifyResetToken, sendVerificationEmail, sendPasswordResetEmail } from '../lib/email.js'; +import { fail } from '../../../shared/lib/logger.js'; import { cookies, headers } from 'next/headers'; import { getSessionCookieName, getPublicBaseUrl } from '../../../shared/lib/appConfig.js'; import { checkRateLimit, getIpFromHeaders, formatRetryAfter } from '../lib/rateLimit.js'; @@ -228,7 +229,7 @@ export async function getSession() { return result; } catch (error) { - console.error('Session validation error:', error); + fail(`Auth: session validation error: ${error.message}`); return null; } } diff --git a/src/features/auth/lib/auth.js b/src/features/auth/lib/auth.js index 768e5fd..9a6dc77 100644 --- a/src/features/auth/lib/auth.js +++ b/src/features/auth/lib/auth.js @@ -6,6 +6,7 @@ import { create, findOne, updateById, count } from '../../../core/database/crud.js'; import { hashPassword, verifyPassword, generateId } from './password.js'; import { createSession } from './session.js'; +import { fail } from '../../../shared/lib/logger.js'; import { createEmailVerification, createPasswordReset, verifyResetToken, deleteResetToken, sendPasswordChangedEmail } from './email.js'; /** @@ -251,7 +252,7 @@ async function resetPassword(resetData) { await sendPasswordChangedEmail(email); } catch (error) { // Log error but don't fail the password reset process - console.error(`[ZEN AUTH] Failed to send password changed email to ${email}:`, error.message); + fail(`Auth: failed to send password changed email to ${email}: ${error.message}`); } return { success: true }; diff --git a/src/features/auth/lib/email.js b/src/features/auth/lib/email.js index 2382983..8e02383 100644 --- a/src/features/auth/lib/email.js +++ b/src/features/auth/lib/email.js @@ -6,6 +6,7 @@ import crypto from 'crypto'; import { create, findOne, deleteWhere } from '../../../core/database/crud.js'; import { generateToken, generateId } from './password.js'; +import { fail, info } from '../../../shared/lib/logger.js'; import { sendAuthEmail } from '../../../core/email/index.js'; import { renderVerificationEmail, renderPasswordResetEmail, renderPasswordChangedEmail } from '../../../core/email/templates/index.js'; @@ -178,11 +179,11 @@ async function sendVerificationEmail(email, token, baseUrl) { }); if (!result.success) { - console.error(`[ZEN AUTH] Failed to send verification email to ${email}:`, result.error); + fail(`Auth: failed to send verification email to ${email}: ${result.error}`); throw new Error('Failed to send verification email'); } - console.log(`[ZEN AUTH] Verification email sent to ${email}`); + info(`Auth: verification email sent to ${email}`); return result; } @@ -205,11 +206,11 @@ async function sendPasswordResetEmail(email, token, baseUrl) { }); if (!result.success) { - console.error(`[ZEN AUTH] Failed to send password reset email to ${email}:`, result.error); + fail(`Auth: failed to send password reset email to ${email}: ${result.error}`); throw new Error('Failed to send password reset email'); } - console.log(`[ZEN AUTH] Password reset email sent to ${email}`); + info(`Auth: password reset email sent to ${email}`); return result; } @@ -229,11 +230,11 @@ async function sendPasswordChangedEmail(email) { }); if (!result.success) { - console.error(`[ZEN AUTH] Failed to send password changed email to ${email}:`, result.error); + fail(`Auth: failed to send password changed email to ${email}: ${result.error}`); throw new Error('Failed to send password changed email'); } - console.log(`[ZEN AUTH] Password changed email sent to ${email}`); + info(`Auth: password changed email sent to ${email}`); return result; } diff --git a/src/modules/init.js b/src/modules/init.js index 9e8b7db..ab513dc 100644 --- a/src/modules/init.js +++ b/src/modules/init.js @@ -7,6 +7,7 @@ */ import { AVAILABLE_MODULES } from './modules.registry.js'; +import { step, done, warn, fail, info } from '../shared/lib/logger.js'; /** * Check if a module is enabled via environment variable @@ -26,16 +27,16 @@ export async function initModules() { const created = []; const skipped = []; - console.log('\nInitializing module databases...'); + step('Initializing module databases...'); for (const moduleName of AVAILABLE_MODULES) { if (!isModuleEnabled(moduleName)) { - console.log(`- Skipped ${moduleName} (not enabled)`); + info(`Skipped ${moduleName} (not enabled)`); continue; } try { - console.log(`\nInitializing ${moduleName} module tables...`); + step(`Initializing ${moduleName}...`); const db = await import(`./${moduleName}/db.js`); if (typeof db.createTables === 'function') { @@ -44,15 +45,15 @@ export async function initModules() { if (result?.created) created.push(...result.created); if (result?.skipped) skipped.push(...result.skipped); - console.log(`โœ“ ${moduleName} module initialized`); + done(`${moduleName} initialized`); } else { - console.log(`- ${moduleName} has no createTables function`); + info(`${moduleName} has no createTables function`); } } catch (error) { if (error.code === 'ERR_MODULE_NOT_FOUND' || error.message?.includes('Cannot find module')) { - console.log(`- ${moduleName} has no db.js (skipped)`); + info(`${moduleName} has no db.js (skipped)`); } else { - console.error(`Error initializing ${moduleName}:`, error.message); + fail(`${moduleName}: ${error.message}`); } } } diff --git a/src/modules/modules.actions.js b/src/modules/modules.actions.js index 91e59ea..9a3a640 100644 --- a/src/modules/modules.actions.js +++ b/src/modules/modules.actions.js @@ -10,6 +10,7 @@ */ import { getModule, getEnabledModules } from '@zen/core/core/modules'; +import { fail } from '../shared/lib/logger.js'; // Static actions for internal modules (add entries here for new internal modules) export const MODULE_ACTIONS = { @@ -60,7 +61,7 @@ export async function getAllModuleDashboardStats() { stats[moduleName] = result.stats; } } catch (error) { - console.error(`Error getting dashboard stats for ${moduleName}:`, error); + fail(`Error getting dashboard stats for ${moduleName}: ${error.message}`); } } @@ -73,7 +74,7 @@ export async function getAllModuleDashboardStats() { stats[mod.name] = result.stats; } } catch (error) { - console.error(`Error getting dashboard stats for ${mod.name}:`, error); + fail(`Error getting dashboard stats for ${mod.name}: ${error.message}`); } } } diff --git a/src/modules/page.js b/src/modules/page.js index ac6af96..4540df6 100644 --- a/src/modules/page.js +++ b/src/modules/page.js @@ -15,6 +15,7 @@ import { PublicPagesLayout, PublicPagesClient } from '@zen/core/modules/pages'; import { getMetadataGenerator } from '@zen/core/modules/metadata'; import { getAppConfig } from '@zen/core'; import { getModuleActions } from '@zen/core/modules/actions'; +import { fail } from '../shared/lib/logger.js'; /** * Per-module path configuration. @@ -62,7 +63,7 @@ export async function generateMetadata({ params }) { try { return await generator(token); } catch (error) { - console.error(`[ZEN] Error generating metadata for ${moduleName}/${metadataType}:`, error); + fail(`Error generating metadata for ${moduleName}/${metadataType}: ${error.message}`); } } } diff --git a/src/modules/posts/api.js b/src/modules/posts/api.js index a9e6d0d..0c389d8 100644 --- a/src/modules/posts/api.js +++ b/src/modules/posts/api.js @@ -32,6 +32,7 @@ import { } from '@zen/core/storage'; import { getPostsConfig, getPostType } from './config.js'; +import { fail, warn } from '../../shared/lib/logger.js'; // ============================================================================ // Config @@ -91,7 +92,7 @@ async function handleGetPosts(request) { limit: result.pagination.limit }; } catch (error) { - console.error('[Posts] Error GET posts:', error); + fail(`Posts: error GET posts: ${error.message}`); return { success: false, error: error.message || 'Failed to fetch posts' }; } } @@ -111,7 +112,7 @@ async function handleCreatePost(request) { const post = await createPost(postType, postData); return { success: true, post, message: 'Post created successfully' }; } catch (error) { - console.error('[Posts] Error creating post:', error); + fail(`Posts: error creating post: ${error.message}`); return { success: false, error: error.message || 'Failed to create post' }; } } @@ -148,7 +149,7 @@ async function handleUpdatePost(request) { try { await deleteFile(oldKey); } catch (err) { - console.warn(`[Posts] Error deleting old image ${oldKey}:`, err.message); + warn(`Posts: error deleting old image ${oldKey}: ${err.message}`); } } } @@ -156,7 +157,7 @@ async function handleUpdatePost(request) { return { success: true, post, message: 'Post updated successfully' }; } catch (error) { - console.error('[Posts] Error updating post:', error); + fail(`Posts: error updating post: ${error.message}`); return { success: false, error: error.message || 'Failed to update post' }; } } @@ -174,7 +175,7 @@ async function handleDeletePost(request) { if (!deleted) return { success: false, error: 'Post not found' }; return { success: true, message: 'Post deleted successfully' }; } catch (error) { - console.error('[Posts] Error deleting post:', error); + fail(`Posts: error deleting post: ${error.message}`); return { success: false, error: 'Failed to delete post' }; } } @@ -221,7 +222,7 @@ async function handleUploadImage(request) { return { success: true, key: uploadResult.data.key }; } catch (error) { - console.error('[Posts] Error uploading image:', error); + fail(`Posts: error uploading image: ${error.message}`); return { success: false, error: error.message || 'Upload failed' }; } } @@ -268,7 +269,7 @@ async function handleGetCategories(request) { limit: result.pagination.limit }; } catch (error) { - console.error('[Posts] Error GET categories:', error); + fail(`Posts: error GET categories: ${error.message}`); return { success: false, error: error.message || 'Failed to fetch categories' }; } } @@ -288,7 +289,7 @@ async function handleCreateCategory(request) { const category = await createCategory(postType, categoryData); return { success: true, category, message: 'Category created successfully' }; } catch (error) { - console.error('[Posts] Error creating category:', error); + fail(`Posts: error creating category: ${error.message}`); return { success: false, error: error.message || 'Failed to create category' }; } } @@ -314,7 +315,7 @@ async function handleUpdateCategory(request) { const category = await updateCategory(postType, parseInt(id), updates); return { success: true, category, message: 'Category updated successfully' }; } catch (error) { - console.error('[Posts] Error updating category:', error); + fail(`Posts: error updating category: ${error.message}`); return { success: false, error: error.message || 'Failed to update category' }; } } @@ -331,7 +332,7 @@ async function handleDeleteCategory(request) { await deleteCategory(postType, parseInt(id)); return { success: true, message: 'Category deleted successfully' }; } catch (error) { - console.error('[Posts] Error deleting category:', error); + fail(`Posts: error deleting category: ${error.message}`); if (error.message.includes('Cannot delete')) { return { success: false, error: error.message }; } @@ -356,7 +357,7 @@ async function handleSearchPosts(request) { const posts = await searchPosts(postType, q, limit); return { success: true, posts }; } catch (error) { - console.error('[Posts] Error searching posts:', error); + fail(`Posts: error searching posts: ${error.message}`); return { success: false, error: error.message || 'Failed to search posts' }; } } @@ -406,7 +407,7 @@ async function handlePublicGetPosts(request, params) { limit: result.pagination.limit }; } catch (error) { - console.error('[Posts] Error public GET posts:', error); + fail(`Posts: error public GET posts: ${error.message}`); return { success: false, error: error.message || 'Failed to fetch posts' }; } } @@ -421,7 +422,7 @@ async function handlePublicGetPostBySlug(request, params) { if (!post) return { success: false, error: 'Post not found' }; return { success: true, post }; } catch (error) { - console.error('[Posts] Error public GET post by slug:', error); + fail(`Posts: error public GET post by slug: ${error.message}`); return { success: false, error: error.message || 'Failed to fetch post' }; } } @@ -434,7 +435,7 @@ async function handlePublicGetCategories(request, params) { const categories = await getActiveCategories(postType); return { success: true, categories }; } catch (error) { - console.error('[Posts] Error public GET categories:', error); + fail(`Posts: error public GET categories: ${error.message}`); return { success: false, error: error.message || 'Failed to fetch categories' }; } } diff --git a/src/modules/posts/crud.js b/src/modules/posts/crud.js index f51c5ca..b7c9376 100644 --- a/src/modules/posts/crud.js +++ b/src/modules/posts/crud.js @@ -6,6 +6,7 @@ import { query } from '@zen/core/database'; import { deleteFile } from '@zen/core/storage'; +import { warn } from '../../shared/lib/logger.js'; import { getPostType } from './config.js'; function slugify(text) { @@ -438,10 +439,10 @@ export async function deletePost(postType, id) { try { const deleteResult = await deleteFile(imageKey); if (!deleteResult.success) { - console.warn(`[Posts] Failed to delete image from storage: ${imageKey}`, deleteResult.error); + warn(`Posts: failed to delete image from storage: ${imageKey} โ€” ${deleteResult.error}`); } } catch (err) { - console.warn(`[Posts] Error deleting image from storage: ${imageKey}`, err.message); + warn(`Posts: error deleting image from storage: ${imageKey} โ€” ${err.message}`); } } diff --git a/src/modules/posts/db.js b/src/modules/posts/db.js index ab586e1..1e470d4 100644 --- a/src/modules/posts/db.js +++ b/src/modules/posts/db.js @@ -5,6 +5,7 @@ import { query } from '@zen/core/database'; import { getPostsConfig } from './config.js'; +import { done, info, step } from '../../shared/lib/logger.js'; async function tableExists(tableName) { const result = await query( @@ -23,7 +24,7 @@ async function createPostsCategoryTable() { const exists = await tableExists(tableName); if (exists) { - console.log(`- Table already exists: ${tableName}`); + info(`Table already exists: ${tableName}`); return { created: false, tableName }; } @@ -42,7 +43,7 @@ async function createPostsCategoryTable() { await query(`CREATE INDEX idx_zen_posts_category_post_type ON zen_posts_category(post_type)`); await query(`CREATE INDEX idx_zen_posts_category_is_active ON zen_posts_category(is_active)`); - console.log(`โœ“ Created table: ${tableName}`); + done(`Created table: ${tableName}`); return { created: true, tableName }; } @@ -51,7 +52,7 @@ async function createPostsTable() { const exists = await tableExists(tableName); if (exists) { - console.log(`- Table already exists: ${tableName}`); + info(`Table already exists: ${tableName}`); return { created: false, tableName }; } @@ -73,7 +74,7 @@ async function createPostsTable() { await query(`CREATE INDEX idx_zen_posts_category_id ON zen_posts(category_id)`); await query(`CREATE INDEX idx_zen_posts_data_gin ON zen_posts USING GIN (data)`); - console.log(`โœ“ Created table: ${tableName}`); + done(`Created table: ${tableName}`); return { created: true, tableName }; } @@ -82,7 +83,7 @@ async function createPostsRelationsTable() { const exists = await tableExists(tableName); if (exists) { - console.log(`- Table already exists: ${tableName}`); + info(`Table already exists: ${tableName}`); return { created: false, tableName }; } @@ -100,7 +101,7 @@ async function createPostsRelationsTable() { await query(`CREATE INDEX idx_zen_posts_relations_post_id ON zen_posts_relations(post_id)`); await query(`CREATE INDEX idx_zen_posts_relations_related ON zen_posts_relations(related_post_id)`); - console.log(`โœ“ Created table: ${tableName}`); + done(`Created table: ${tableName}`); return { created: true, tableName }; } @@ -119,18 +120,18 @@ export async function createTables() { // zen_posts_category must always be created before zen_posts // because zen_posts has a FK reference to it - console.log('\n--- Posts Categories ---'); + step('Posts Categories'); const catResult = await createPostsCategoryTable(); if (catResult.created) created.push(catResult.tableName); else skipped.push(catResult.tableName); - console.log('\n--- Posts ---'); + step('Posts'); const postResult = await createPostsTable(); if (postResult.created) created.push(postResult.tableName); else skipped.push(postResult.tableName); if (needsRelations) { - console.log('\n--- Posts Relations ---'); + step('Posts Relations'); const relResult = await createPostsRelationsTable(); if (relResult.created) created.push(relResult.tableName); else skipped.push(relResult.tableName); diff --git a/src/shared/lib/init.js b/src/shared/lib/init.js index f2768a4..acd7a64 100644 --- a/src/shared/lib/init.js +++ b/src/shared/lib/init.js @@ -4,6 +4,7 @@ */ import { discoverModules, registerExternalModules, startModuleCronJobs, stopModuleCronJobs } from '../../core/modules/index.js'; +import { step, done, warn, fail } from './logger.js'; // Use globalThis to persist initialization flag across module reloads const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__'); @@ -11,10 +12,10 @@ const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__'); /** * Initialize ZEN system * Discovers modules dynamically and starts cron jobs - * + * * Recommended: Use instrumentation.js for automatic initialization * Alternative: Call this function manually in your root layout - * + * * @example * // instrumentation.js (Recommended) โ€” internal modules only * export async function register() { @@ -42,26 +43,26 @@ const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__'); */ export async function initializeZen(config = {}) { const { modules: externalModules = [], skipCron = false, skipDb = true } = config; - + // Only run on server-side if (typeof window !== 'undefined') { return { skipped: true, reason: 'client-side' }; } - + // Prevent multiple initializations using globalThis if (globalThis[ZEN_INIT_KEY]) { - console.log('โš  ZEN: Already initialized, skipping...'); + warn('ZEN: already initialized, skipping'); return { skipped: true, reason: 'already-initialized' }; } - + globalThis[ZEN_INIT_KEY] = true; - console.log('๐Ÿš€ ZEN: Starting initialization...'); - + step('ZEN starting...'); + const result = { discovery: null, cron: { started: [], errors: [] } }; - + try { // Step 1: Discover and register internal modules (from modules.registry.js) result.discovery = await discoverModules(); @@ -70,10 +71,10 @@ export async function initializeZen(config = {}) { const skippedCount = result.discovery.skipped?.length || 0; if (enabledCount > 0) { - console.log(`โœ“ ZEN: Discovered ${enabledCount} internal module(s): ${result.discovery.enabled.join(', ')}`); + done(`ZEN: ${enabledCount} module(s): ${result.discovery.enabled.join(', ')}`); } if (skippedCount > 0) { - console.log(`โš  ZEN: Skipped ${skippedCount} disabled module(s): ${result.discovery.skipped.join(', ')}`); + warn(`ZEN: skipped ${skippedCount} module(s): ${result.discovery.skipped.join(', ')}`); } // Step 2: Register external modules from zen.config.js (if any) @@ -81,26 +82,26 @@ export async function initializeZen(config = {}) { 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(', ')}`); + done(`ZEN: ${result.external.registered.length} external module(s): ${result.external.registered.join(', ')}`); } } - + // Step 3: Start cron jobs for all enabled modules (internal + external) if (!skipCron) { result.cron = await startModuleCronJobs(); - + if (result.cron.started.length > 0) { - console.log(`โœ“ ZEN: Started ${result.cron.started.length} cron job(s): ${result.cron.started.join(', ')}`); + done(`ZEN: ${result.cron.started.length} cron job(s): ${result.cron.started.join(', ')}`); } } - - console.log('โœ“ ZEN: Initialization complete'); - + + done('ZEN: ready'); + } catch (error) { - console.error('โœ— ZEN: Initialization failed:', error); + fail(`ZEN: init failed: ${error.message}`); result.error = error.message; } - + return result; } @@ -110,14 +111,13 @@ export async function initializeZen(config = {}) { */ export function resetZenInitialization() { globalThis[ZEN_INIT_KEY] = false; - + // Stop all cron jobs using the module system try { stopModuleCronJobs(); } catch (e) { // Cron system not available } - - console.log('โš  ZEN: Initialization flag reset'); -} + warn('ZEN: initialization reset'); +} diff --git a/src/shared/lib/logger.js b/src/shared/lib/logger.js new file mode 100644 index 0000000..cf91127 --- /dev/null +++ b/src/shared/lib/logger.js @@ -0,0 +1,30 @@ +/** + * ZEN Console Logger + * Centralized, styled logging for server-side output. + * Inspired by the zen-start CLI style. + */ + +const c = { + reset: '\x1b[0m', + dim: '\x1b[2m', + bold: '\x1b[1m', + green: '\x1b[32m', + yellow: '\x1b[33m', + red: '\x1b[31m', + cyan: '\x1b[36m', +}; + +const out = (msg) => process.stdout.write(msg + '\n'); + +/** In-progress step โ€” dim โ—† */ +export const step = (msg) => out(` ${c.dim}โ—†${c.reset} ${msg}`); +/** Success โ€” green โœ“ */ +export const done = (msg) => out(` ${c.green}โœ“${c.reset} ${msg}`); +/** Warning โ€” yellow โš  */ +export const warn = (msg) => out(` ${c.yellow}โš ${c.reset} ${msg}`); +/** Error / failure โ€” red โœ— */ +export const fail = (msg) => out(` ${c.red}โœ—${c.reset} ${msg}`); +/** Sub-item detail โ€” dim ยท */ +export const info = (msg) => out(` ${c.dim}ยท${c.reset} ${msg}`); + +export const logger = { step, done, warn, fail, info };