refactor: replace console.log with structured logger calls

Replace raw `console.log`/`console.error` calls across CLI, API
handlers, and module files with structured logger functions (`step`,
`done`, `warn`, `fail`) from the shared logger library.

This improves log consistency, readability, and makes it easier to
control output formatting and log levels from a single place.
This commit is contained in:
2026-04-12 21:44:00 -04:00
parent dd7c54d913
commit e87bd05fa4
25 changed files with 218 additions and 189 deletions
+13 -12
View File
@@ -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);
}
}
+2 -1
View File
@@ -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'
+7 -6
View File
@@ -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}`);
}
}
+6 -5
View File
@@ -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',
+4 -3
View File
@@ -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,
+10 -10
View File
@@ -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
}
+5 -4
View File
@@ -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;
}
}
+10 -12
View File
@@ -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<Object>} 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 {
+7 -6
View File
@@ -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,
+12 -24
View File
@@ -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];
+18 -18
View File
@@ -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<Object>} 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<Object>} 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();
+13 -14
View File
@@ -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 };
}
}
+2 -5
View File
@@ -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,
+3 -2
View File
@@ -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'
+2 -1
View File
@@ -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;
}
}
+2 -1
View File
@@ -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 };
+7 -6
View File
@@ -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;
}
+8 -7
View File
@@ -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}`);
}
}
}
+3 -2
View File
@@ -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}`);
}
}
}
+2 -1
View File
@@ -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}`);
}
}
}
+15 -14
View File
@@ -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' };
}
}
+3 -2
View File
@@ -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}`);
}
}
+10 -9
View File
@@ -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);
+24 -24
View File
@@ -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');
}
+30
View File
@@ -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 };