meocord
Version:
Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.
180 lines (173 loc) • 6.75 kB
JavaScript
;
var node_util = require('node:util');
var dayjs = require('dayjs');
var utc = require('dayjs/plugin/utc.js');
var timezone = require('dayjs/plugin/timezone.js');
var path = require('path');
var fs = require('fs');
var jiti = require('jiti');
var chalk = require('chalk');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
/**
* MeoCord Framework
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
* SPDX-License-Identifier: MIT
*/ /**
* Helper function to fix common JSON formatting issues in tsconfig.json, such as:
* - Removing single-line comments.
* - Removing trailing commas.
* - Stripping newlines.
*
* @param {string} jsonString - The raw JSON string to fix.
* @returns {string} The corrected JSON string.
*/ function fixJSON(jsonString) {
return jsonString.replace(/\/\/.*$/gm, '') // Remove single-line comments
.replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas before } or ]
.replace(/,\s*$/, '') // Remove trailing commas at the end of the file
.replace(/^\s*[\r\n]/gm, '') // Replace empty lines only
;
}
let cachedConfig;
let configLoaded = false;
/**
* Loads the MeoCord configuration, checking compiled output first then falling back to source.
*
* Resolution order:
* 1. `dist/meocord.config.mjs` — pre-compiled by `meocord build`, no tsconfig or source files needed
* 2. `meocord.config.ts` — loaded via jiti with tsconfig path aliases (dev mode)
*
* The result is cached after the first successful load.
*
* @returns {MeoCordConfig | undefined} The loaded configuration object, or undefined if loading fails.
*/ function loadMeoCordConfig() {
if (configLoaded) return cachedConfig;
configLoaded = true;
cachedConfig = loadCompiledConfig() ?? loadSourceConfig();
return cachedConfig;
}
/**
* Loads the pre-compiled config from dist/meocord.config.mjs.
* This file is generated by `meocord build` and requires no tsconfig or source files.
*/ function loadCompiledConfig() {
const compiledPath = path.resolve(process.cwd(), 'dist', 'meocord.config.mjs');
if (!fs.existsSync(compiledPath)) return undefined;
try {
const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/theme-Bz-D4RbT.cjs', document.baseURI).href)), {
interopDefault: true
});
return jiti$1(compiledPath);
} catch {
// Fall through to source config
return undefined;
}
}
/**
* Loads the source config from meocord.config.ts via jiti with tsconfig path alias resolution.
* Used in development mode where source files and tsconfig.json are available.
*/ function loadSourceConfig() {
const configPath = path.resolve(process.cwd(), 'meocord.config.ts');
if (!fs.existsSync(configPath)) return undefined;
try {
const tsConfigPath = path.resolve(process.cwd(), 'tsconfig.json');
const aliases = {};
if (fs.existsSync(tsConfigPath)) {
const tsConfig = JSON.parse(fixJSON(fs.readFileSync(tsConfigPath, 'utf-8')));
const paths = tsConfig?.compilerOptions?.paths;
if (paths) {
for (const [key, values] of Object.entries(paths)){
const aliasKey = key.replace('/*', '');
aliases[aliasKey] = path.resolve(process.cwd(), values[0].replace('/*', ''));
}
}
}
const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/theme-Bz-D4RbT.cjs', document.baseURI).href)), {
interopDefault: true,
alias: aliases,
moduleCache: false
});
return jiti$1(configPath);
} catch (error) {
if (error instanceof Error) {
console.error(`[MeoCord] Failed to load config: ${error.message}`);
} else {
console.error(`[MeoCord] Failed to load config: Unknown error`);
}
return undefined;
}
}
dayjs.extend(utc);
dayjs.extend(timezone);
class Logger {
log(...args) {
this.logWithContext('log', args);
}
info(...args) {
this.logWithContext('log', args);
}
warn(...args) {
this.logWithContext('warn', args);
}
error(...args) {
this.logWithContext('error', args);
}
debug(...args) {
this.logWithContext('debug', args);
}
verbose(...args) {
this.logWithContext('log', args);
}
formatMessage(message, logType) {
if (typeof message === 'object' && message !== null) {
return node_util.inspect(message, {
showHidden: true,
depth: null,
colors: true,
compact: false,
showProxy: true
});
}
return (this.colorMap[logType] || ((msg)=>msg))(message);
}
logWithContext(logLevel, messages) {
if (messages.length === 0) return;
const config = loadMeoCordConfig();
const logType = logLevel.toUpperCase();
const applyColor = this.colorMap[logType] || ((msg)=>msg);
const formattedMessages = messages.map((message)=>this.formatMessage(message, logType));
const coloredAppName = config?.appName ? applyColor(chalk.bold(`[${config.appName}]`)) : undefined;
const timestamp = chalk.bold(dayjs().format('dddd, MMMM D, YYYY HH:mm:ss [UTC]Z'));
const coloredLogLevel = applyColor(chalk.bold(`[${logType}]`));
const coloredContext = this.context ? chalk.yellow.bold(`[${this.context}]`) : '';
const logTexts = [
coloredAppName,
timestamp,
coloredLogLevel,
coloredContext,
...formattedMessages
].filter((log)=>!!log);
console[logLevel](...logTexts);
}
constructor(context){
this.context = context;
this.colorMap = {
LOG: chalk.green,
INFO: chalk.cyan,
WARN: chalk.yellow,
ERROR: chalk.red,
DEBUG: chalk.magenta
};
}
}
/**
* MeoCord Framework
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
* SPDX-License-Identifier: MIT
*/ class Theme {
}
Theme.successColor = '#28A745';
Theme.infoColor = '#17A2B8';
Theme.errorColor = '#DC3545';
Theme.warningColor = '#FFC107';
exports.Logger = Logger;
exports.Theme = Theme;
exports.loadMeoCordConfig = loadMeoCordConfig;