dscaffold
Version:
A TypeScript framework for scaffolding modular Discord bot projects with dynamic command and event loading
238 lines • 10.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports.dscaffold = exports.Dscaffold = void 0;
const discord_js_1 = require("discord.js");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class Dscaffold {
constructor() {
this.commands = new discord_js_1.Collection();
this.categories = new discord_js_1.Collection();
}
/**
* Dynamically load commands from a directory
* @param client - Discord.js Client instance
* @param commandsPath - Path to commands directory (relative to project root)
* @param options - Loading options
*/
async loadCommands(client, commandsPath, options = {}) {
const { recursive = true, fileExtensions = ['.js', '.ts'], excludeDirs = ['node_modules', '.git', 'dist'] } = options;
const resolvedPath = path_1.default.resolve(commandsPath);
if (!fs_1.default.existsSync(resolvedPath)) {
throw new Error(`Commands directory not found: ${resolvedPath}`);
}
await this.loadCommandsRecursive(client, resolvedPath, recursive, fileExtensions, excludeDirs);
console.log(`✅ Loaded ${this.commands.size} commands from ${commandsPath}`);
// Log categories
this.categories.forEach((commands, category) => {
console.log(` 📁 ${category}: ${commands.length} commands`);
});
}
/**
* Dynamically load events from a directory
* @param client - Discord.js Client instance
* @param eventsPath - Path to events directory (relative to project root)
* @param options - Loading options
*/
async loadEvents(client, eventsPath, options = {}) {
const { recursive = true, fileExtensions = ['.js', '.ts'], excludeDirs = ['node_modules', '.git', 'dist'] } = options;
const resolvedPath = path_1.default.resolve(eventsPath);
if (!fs_1.default.existsSync(resolvedPath)) {
throw new Error(`Events directory not found: ${resolvedPath}`);
}
let eventCount = 0;
await this.loadEventsRecursive(client, resolvedPath, recursive, fileExtensions, excludeDirs, (count) => {
eventCount = count;
});
console.log(`✅ Loaded ${eventCount} events from ${eventsPath}`);
}
/**
* Get command by name
*/
getCommand(name) {
return this.commands.get(name);
}
/**
* Get commands by category
*/
getCommandsByCategory(category) {
return this.categories.get(category) || [];
}
/**
* Get all categories
*/
getCategories() {
return Array.from(this.categories.keys());
}
async loadCommandsRecursive(client, dirPath, recursive, fileExtensions, excludeDirs) {
const items = fs_1.default.readdirSync(dirPath);
for (const item of items) {
const itemPath = path_1.default.join(dirPath, item);
const stat = fs_1.default.statSync(itemPath);
if (stat.isDirectory()) {
if (recursive && !excludeDirs.includes(item)) {
await this.loadCommandsRecursive(client, itemPath, recursive, fileExtensions, excludeDirs);
}
}
else if (stat.isFile()) {
const ext = path_1.default.extname(item);
if (fileExtensions.includes(ext)) {
try {
// Clear require cache for hot reloading in development
delete require.cache[require.resolve(itemPath)];
const commandModule = require(itemPath);
// Handle both module.exports and export default
const command = commandModule.default || commandModule;
if (this.isValidCommand(command)) {
this.commands.set(command.name, command);
// Organize by category
const category = command.category || 'general';
if (!this.categories.has(category)) {
this.categories.set(category, []);
}
this.categories.get(category).push(command);
console.log(` 📄 Loaded command: ${command.name} (${category})`);
}
else {
console.warn(` ⚠️ Invalid command file: ${itemPath}`);
}
}
catch (error) {
console.error(` ❌ Error loading command from ${itemPath}:`, error);
}
}
}
}
}
async loadEventsRecursive(client, dirPath, recursive, fileExtensions, excludeDirs, onEventLoad) {
const items = fs_1.default.readdirSync(dirPath);
let eventCount = 0;
for (const item of items) {
const itemPath = path_1.default.join(dirPath, item);
const stat = fs_1.default.statSync(itemPath);
if (stat.isDirectory()) {
if (recursive && !excludeDirs.includes(item)) {
await this.loadEventsRecursive(client, itemPath, recursive, fileExtensions, excludeDirs, onEventLoad);
}
}
else if (stat.isFile()) {
const ext = path_1.default.extname(item);
if (fileExtensions.includes(ext)) {
try {
// Clear require cache for hot reloading in development
delete require.cache[require.resolve(itemPath)];
const eventModule = require(itemPath);
// Handle both module.exports and export default
const event = eventModule.default || eventModule;
if (this.isValidEvent(event)) {
if (event.once) {
client.once(event.name, (...args) => event.execute(...args));
}
else {
client.on(event.name, (...args) => event.execute(...args));
}
eventCount++;
console.log(` 📄 Loaded event: ${event.name} (${event.once ? 'once' : 'on'})`);
}
else {
console.warn(` ⚠️ Invalid event file: ${itemPath}`);
}
}
catch (error) {
console.error(` ❌ Error loading event from ${itemPath}:`, error);
}
}
}
}
onEventLoad(eventCount);
}
isValidCommand(command) {
return (command &&
typeof command === 'object' &&
typeof command.name === 'string' &&
typeof command.execute === 'function');
}
isValidEvent(event) {
return (event &&
typeof event === 'object' &&
typeof event.name === 'string' &&
typeof event.execute === 'function');
}
/**
* Reload commands (useful for development)
*/
async reloadCommands(client, commandsPath) {
this.commands.clear();
this.categories.clear();
await this.loadCommands(client, commandsPath);
}
/**
* Reload a specific command
*/
async reloadCommand(commandName, commandsPath) {
// Find the command file
const commandFile = this.findCommandFile(commandName, commandsPath);
if (!commandFile) {
return false;
}
try {
// Clear require cache
delete require.cache[require.resolve(commandFile)];
const commandModule = require(commandFile);
const command = commandModule.default || commandModule;
if (this.isValidCommand(command)) {
this.commands.set(command.name, command);
console.log(`✅ Reloaded command: ${command.name}`);
return true;
}
}
catch (error) {
console.error(`❌ Error reloading command ${commandName}:`, error);
}
return false;
}
findCommandFile(commandName, basePath) {
const searchPaths = [
path_1.default.join(basePath, `${commandName}.js`),
path_1.default.join(basePath, `${commandName}.ts`),
path_1.default.join(basePath, `${commandName}/index.js`),
path_1.default.join(basePath, `${commandName}/index.ts`),
];
for (const searchPath of searchPaths) {
if (fs_1.default.existsSync(searchPath)) {
return searchPath;
}
}
// Recursive search
return this.findCommandFileRecursive(commandName, basePath);
}
findCommandFileRecursive(commandName, dirPath) {
if (!fs_1.default.existsSync(dirPath))
return null;
const items = fs_1.default.readdirSync(dirPath);
for (const item of items) {
const itemPath = path_1.default.join(dirPath, item);
const stat = fs_1.default.statSync(itemPath);
if (stat.isDirectory()) {
const result = this.findCommandFileRecursive(commandName, itemPath);
if (result)
return result;
}
else if (stat.isFile()) {
const name = path_1.default.basename(item, path_1.default.extname(item));
if (name === commandName) {
return itemPath;
}
}
}
return null;
}
}
exports.Dscaffold = Dscaffold;
// Create a singleton instance
exports.dscaffold = new Dscaffold();
exports.default = exports.dscaffold;
//# sourceMappingURL=dscaffold.js.map