@uisap/core
Version: 
A modular Fastify-based framework inspired by Laravel
945 lines (899 loc) • 30 kB
JavaScript
import dotenv from "dotenv";
import { program } from "commander";
import {
  Application,
  Config,
  ScheduleFacade,
  QueueFacade,
  BroadcastFacade,
  DatabaseProvider,
  RouteProvider,
  BroadcastProvider,
  QueueProvider,
  Command,
} from "@uisap/core";
import * as path from "path";
import { pathToFileURL } from "url";
import { fileURLToPath } from "url";
import { dirname } from "path";
import fs from "fs-extra";
import chalk from "chalk";
import { spawn } from "child_process";
async function loadCommands() {
  const commandsDir = path.join(process.cwd(), "app/console/commands");
  try {
    const files = await fs.readdir(commandsDir);
    const commandFiles = files.filter((file) => file.endsWith(".js"));
    for (const file of commandFiles) {
      const filePath = pathToFileURL(path.join(commandsDir, file)).href;
      try {
        const commandModule = await import(filePath);
        const CommandClass = commandModule.default;
        if (CommandClass && CommandClass.prototype instanceof Command) {
          const config = await Config.load();
          const app = new Application({
            ...config.app,
            database: config.database, // Veritabanı yapılandırmasını ekle
            queue: config.queue, // Kuyruk yapılandırmasını ekle
            routes: config.routes,
          });
          app.fastify.config = config;
          app.provider(DatabaseProvider);
          app.provider(QueueProvider);
          app.provider(RouteProvider);
          try {
            console.log(`Bootstrapping for command: ${file}`);
            await app.bootstrap();
            console.log(`Bootstrap completed for command: ${file}`);
          } catch (bootstrapErr) {
            console.error(
              chalk.red(
                `Bootstrap failed for command ${file}: ${bootstrapErr.message}`
              )
            );
            console.error(bootstrapErr.stack);
            await app.close();
            continue;
          }
          // Komut örneği oluşturma ve kayıt
          let tempCommandInstance = new CommandClass(app.fastify);
          const signature = tempCommandInstance.signature;
          const description = tempCommandInstance.description;
          program
            .command(signature)
            .description(description)
            .option("--help", "Display help for this command")
            .action(async (options) => {
              if (options.help) {
                console.log(chalk.cyan(`Help for "${signature}":`));
                console.log(`  Description: ${description}`);
                console.log(`  Usage: ${signature}`);
                return;
              }
              try {
                const config = await Config.load();
                const app = new Application({
                  ...config.app,
                  database: config.database, // Veritabanı yapılandırmasını ekle
                  queue: config.queue, // Kuyruk yapılandırmasını ekle
                  routes: config.routes
                });
                app.fastify.config = config;
                app.provider(DatabaseProvider);
                app.provider(QueueProvider);
                app.provider(RouteProvider);
                console.log("Starting bootstrap...");
                await app.bootstrap();
                console.log("Bootstrap completed, running command...");
                const commandInstance = new CommandClass(app.fastify);
                await commandInstance.handle(options);
                console.log("Command executed, closing app...");
                await app.close();
                console.log("App closed, exiting process...");
                process.exit(0);
              } catch (err) {
                console.error(
                  chalk.red(`Command execution failed: ${err.message}`)
                );
                console.error(err.stack);
                process.exit(1);
              }
            });
          console.log(chalk.blue(`Komut yüklendi: ${signature}`));
          await app.close();
        }
      } catch (err) {
        console.error(
          chalk.red(`Failed to load command ${file}: ${err.message}`)
        );
        console.error(err.stack);
        continue;
      }
    }
  } catch (err) {
    if (err.code !== "ENOENT") {
      console.error(chalk.red(`Komutlar yüklenirken hata: ${err.message}`));
      console.error(err.stack);
    }
  }
}
console.log(chalk.blue("UISAP CLI çalıştırıldı"));
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const templates = {
  controller: {
    type: "controller",
    dir: "app/controllers",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
import { BaseController } from "@uisap/core";
export class ${capitalize(name)}Controller extends BaseController {
  constructor(fastify, container) {
    super(fastify, container);
  }
  async index(request, reply) {
    reply.send({ message: "${capitalize(name)} controller works!" });
  }
}
`,
  },
  model: {
    type: "model",
    dir: "app/models",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
import { Model } from "@uisap/core";
export class ${capitalize(name)} extends Model {
  constructor(fastify) {
    super(fastify);
  }
  async getData() {
    return await this.rawSql("SELECT 1 AS test");
  }
}
`,
  },
  middleware: {
    type: "middleware",
    dir: "app/middlewares",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
import { Middleware } from "@uisap/core";
export class ${capitalize(name)} extends Middleware {
  async handle(request, reply, next) {
    return reply.code(401).send({ error: "Unauthorized" });
  }
}
`,
  },
  job: {
    type: "job",
    dir: "app/jobs",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
export class ${capitalize(name)} {
  constructor(data) {
    this.data = data;
  }
  async handle() {
    console.log("${capitalize(name)} job running:", this.data);
  }
}
`,
  },
  event: {
    type: "event",
    dir: "app/events",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
import { Event } from "@uisap/core";
export class ${capitalize(name)} extends Event {
  constructor(data) {
    super(data);
  }
  broadcastOn() {
    return ["${name.toLowerCase()}"];
  }
  broadcastWith() {
    return this.data;
  }
  broadcastAs() {
    return "${capitalize(name)}";
  }
}
`,
  },
  listener: {
    type: "listener",
    dir: "app/listeners",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
export default class ${capitalize(name)} {
  constructor() {
    this.queueName = null;
  }
  onQueue(queueName = "default") {
    this.queueName = queueName;
    return this;
  }
  async handle(event, app) {
    app.fastify.logger.info("${capitalize(name)} handling event:", event);
  }
  async dispatch(event, app) {
    if (this.queueName) {
      await app.container.make("queue").addTo(this.queueName, "${capitalize(
        name
      )}", { eventData: event });
    } else {
      await this.handle(event, app);
    }
  }
}
`,
  },
  command: {
    type: "command",
    dir: "app/console/commands",
    fileName: (name) => `${capitalize(name)}.js`,
    content: (name) => `
import { Command } from "@uisap/core";
export default class ${capitalize(name)} extends Command {
  constructor(fastify) {
    super(fastify);
    this.signature = "${name.toLowerCase()}";
    this.description = "Description of ${capitalize(name)}";
  }
  async handle(options = {}) {
    this.fastify.logger.info("${capitalize(name)} command executed");
  }
}
`,
  },
};
const generateFile = async (template, name) => {
  const { dir, fileName, content } = template;
  const filePath = path.join(process.cwd(), dir, fileName(name));
  try {
    await fs.ensureDir(path.dirname(filePath));
    await fs.writeFile(filePath, content(name).trim());
    console.log(
      chalk.green(`${capitalize(template.type)} created: ${filePath}`)
    );
  } catch (err) {
    console.error(chalk.red(`Error: ${err.message}`));
  }
};
const updateConfig = async (type, name, filePath) => {
  const configPath = path.join(process.cwd(), "config", `${type}.js`);
  const importStatement = `import { join } from "path";\n`;
  const relativePath = filePath.split(process.cwd())[1].replace(/\\/g, "/");
  try {
    let configContent;
    const fileExists = await fs.pathExists(configPath);
    if (!fileExists) {
      configContent =
        type === "schedule"
          ? `${importStatement}\nexport default {\n  commands: {\n    "${name.toLowerCase()}": join(process.cwd(), "${relativePath}")\n  }\n};\n`
          : `${importStatement}\nexport default {\n  enabled: true,\n  redis: {\n    host: process.env.REDIS_HOST || "localhost",\n    port: Number(process.env.REDIS_PORT) || 6379\n  },\n  connections: {\n    default: {},\n    broadcast: {},\n    console: {}\n  },\n  handlers: {\n    ${capitalize(
              name
            )}: join(process.cwd(), "${relativePath}")\n  }\n};\n`;
    } else {
      configContent = await fs.readFile(configPath, "utf-8");
      if (!configContent.includes('import { join } from "path"')) {
        configContent = importStatement + configContent;
      }
      const newEntry =
        type === "schedule"
          ? `"${name.toLowerCase()}": join(process.cwd(), "${relativePath}")`
          : `${capitalize(name)}: join(process.cwd(), "${relativePath}")`;
      if (type === "schedule") {
        if (configContent.includes("commands:")) {
          configContent = configContent.replace(
            /commands:\s*{\s*([^}]*?)\s*}/,
            (match, p1) => {
              const entries = p1
                .split(/,\s*\n/)
                .map((line) => line.trim())
                .filter(Boolean)
                .concat(newEntry);
              return `commands: {\n    ${entries.join(",\n    ")}\n  }`;
            }
          );
        } else {
          configContent = configContent.replace(
            /export default\s*{([^}]*)}/,
            (match, p1) => {
              const trimmedP1 = p1.trim();
              return `export default {\n${trimmedP1}${
                trimmedP1 ? ",\n" : ""
              }  commands: {\n    ${newEntry}\n  }\n}`;
            }
          );
        }
      } else if (type === "queue") {
        if (configContent.includes("handlers:")) {
          configContent = configContent.replace(
            /handlers:\s*{\s*([^}]*?)\s*}/,
            (match, p1) => {
              const entries = p1
                .split(/,\s*\n/)
                .map((line) => line.trim())
                .filter(Boolean)
                .concat(newEntry);
              return `handlers: {\n    ${entries.join(",\n    ")}\n  }`;
            }
          );
        } else {
          configContent = configContent.replace(
            /export default\s*{([^}]*)}/,
            (match, p1) => {
              const trimmedP1 = p1.trim();
              return `export default {\n${trimmedP1}${
                trimmedP1 ? ",\n" : ""
              }  handlers: {\n    ${newEntry}\n  }\n}`;
            }
          );
        }
      }
    }
    const formattedContent = configContent
      .split("\n")
      .map((line) => line.trimEnd())
      .join("\n")
      .replace(/,\s*\n\s*}/g, "\n  }")
      .replace(/}\s*}\s*;/g, "}\n};");
    await fs.writeFile(configPath, formattedContent);
    console.log(chalk.green(`${type}.js updated with ${name}`));
  } catch (err) {
    console.error(chalk.red(`Failed to update ${type}.js: ${err.message}`));
  }
};
const updateAppServiceProvider = async (eventName, listenerName) => {
  const providerPath = path.join(
    process.cwd(),
    "app/providers/AppServiceProvider.js"
  );
  const importCore = `import { ServiceProvider, EventFacade as Event } from '@uisap/core';\n`;
  const importListener = `import ${capitalize(
    listenerName
  )} from '../listeners/${capitalize(listenerName)}.js';\n`;
  const listenStatement = `    Event.listen('${capitalize(
    eventName
  )}', new ${capitalize(listenerName)}(), 10);\n`;
  try {
    let content;
    const fileExists = await fs.pathExists(providerPath);
    if (!fileExists) {
      content = `${importCore}${importListener}
export class AppServiceProvider extends ServiceProvider {
  register() {
    // You can register your services here
  }
  async boot() {
    // You can boot your services here
${listenStatement}
  }
}
`;
    } else {
      content = await fs.readFile(providerPath, "utf-8");
      if (!content.includes(`import ${capitalize(listenerName)} from`)) {
        if (content.includes(importCore)) {
          content = content.replace(
            /import { ServiceProvider, EventFacade as Event } from '@uisap\/core';/,
            `${importCore}${importListener}`
          );
        } else {
          content = `${importCore}${importListener}${content}`;
        }
      }
      if (content.includes("async boot()")) {
        content = content.replace(/async boot\(\) {([^}]*)}/, (match, p1) => {
          const trimmedP1 = p1.trim();
          const entries = trimmedP1
            ? `${trimmedP1}\n${listenStatement}`
            : `${listenStatement}`;
          return `async boot() {\n${entries}  }`;
        });
      } else {
        content = content.replace(
          /export class AppServiceProvider extends ServiceProvider {([^}]*)}/,
          `export class AppServiceProvider extends ServiceProvider {\n$1\n  async boot() {\n${listenStatement}  }\n}`
        );
      }
    }
    const formattedContent = content
      .split("\n")
      .map((line) => line.trimEnd())
      .join("\n");
    await fs.writeFile(providerPath, formattedContent);
    console.log(
      chalk.green(
        `AppServiceProvider updated with ${eventName} and ${listenerName}`
      )
    );
  } catch (err) {
    console.error(
      chalk.red(`Failed to update AppServiceProvider: ${err.message}`)
    );
  }
};
program
  .version("1.0.5")
  .description("UISAP - Fastify framework CLI tool")
  .usage("[command] [options]");
// Global Help Command
program
  .command("help")
  .description("Display detailed help information for all commands")
  .action(() => {
    console.log(chalk.cyan.bold("\nUISAP CLI Help\n"));
    console.log(chalk.white("Version: 1.0.3"));
    console.log(chalk.white("Description: Fastify framework CLI tool"));
    console.log(chalk.white("Usage: uisap <command> [options]\n"));
    console.log(chalk.cyan("Available Commands:\n"));
    const commands = [
      {
        name: "schedule:list",
        desc: "List all scheduled tasks",
        usage: "schedule:list",
        options: [],
      },
      {
        name: "serve",
        desc: "Start the Fastify application with npm run dev",
        usage: "serve [-p, --port <port>]",
        options: ["-p, --port <port>  Port number (default: 4115)"],
      },
      {
        name: "schedule:run",
        desc: "Run all scheduled tasks once",
        usage: "schedule:run",
        options: [],
      },
      {
        name: "queue:work",
        desc: "Process jobs from the queue",
        usage: "queue:work [--queue <queue>] [--tries <tries>]",
        options: [
          "--queue <queue>  Queue to process (default: 'default')",
          "--tries <tries>  Number of retries (default: 3)",
        ],
      },
      {
        name: "broadcast:test",
        desc: "Send a test broadcast message",
        usage:
          "broadcast:test [--channel <channel>] [--event <event>] [--data <data>]",
        options: [
          "--channel <channel>  Channel name (default: 'my-channel')",
          "--event <event>      Event name (default: 'my-event')",
          "--data <data>        Data to send (default: 'Test message')",
        ],
      },
      {
        name: "make:controller <name>",
        desc: "Create a new controller class",
        usage: "make:controller <name>",
        options: [],
      },
      {
        name: "make:model <name>",
        desc: "Create a new model class",
        usage: "make:model <name>",
        options: [],
      },
      {
        name: "make:middleware <name>",
        desc: "Create a new middleware class",
        usage: "make:middleware <name>",
        options: [],
      },
      {
        name: "make:job <name>",
        desc: "Create a new job class",
        usage: "make:job <name>",
        options: [],
      },
      {
        name: "make:event <name>",
        desc: "Create a new event class",
        usage: "make:event <name> [--listener <listener>]",
        options: [
          "--listener <listener>  Add to AppServiceProvider with specified listener",
        ],
      },
      {
        name: "make:listener <name>",
        desc: "Create a new listener class",
        usage: "make:listener <name> [--queue] [--event <event>]",
        options: [
          "--queue          Add to config/queue.js as handler",
          "--event <event>  Add to AppServiceProvider with specified event",
        ],
      },
      {
        name: "make:command <name>",
        desc: "Create a new console command",
        usage: "make:command <name> [--schedule]",
        options: ["--schedule  Add to config/schedule.js"],
      },
      {
        name: "help",
        desc: "Display this help information",
        usage: "help",
        options: [],
      },
    ];
    commands.forEach((cmd) => {
      console.log(chalk.green(`  ${cmd.name}`));
      console.log(`    Description: ${cmd.desc}`);
      console.log(`    Usage: ${cmd.usage}`);
      if (cmd.options.length > 0) {
        console.log("    Options:");
        cmd.options.forEach((opt) => console.log(`      ${opt}`));
      }
      console.log();
    });
    console.log(chalk.cyan("Notes:"));
    console.log(
      "  - Use `<name>` for required arguments and `[options]` for optional ones."
    );
    console.log(
      "  - Run any command with `--help` to see its specific help (if available).\n"
    );
  });
program
  .command("schedule:list")
  .description("List all scheduled tasks")
  .option("--help", "Display help for this command")
  .action(async (options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'schedule:list':"));
      console.log("  Description: List all scheduled tasks");
      console.log("  Usage: schedule:list");
      console.log("  No additional options available.");
      return;
    }
    try {
      const config = await Config.load();
      const app = new Application({
        ...config.app,
        routes: {
          console: (
            await import(
              pathToFileURL(path.join(process.cwd(), "routes/console.js")).href
            )
          ).default,
        },
      });
      app.fastify.config = config;
      app.provider(QueueProvider);
      app.provider(RouteProvider);
      await app.bootstrap();
      await app.routes.console(app);
      const tasks = ScheduleFacade.listTasks();
      if (tasks.length === 0) {
        console.log(chalk.yellow("No scheduled tasks found."));
      } else {
        console.table(tasks);
      }
    } catch (err) {
      console.error(chalk.red(`Error listing schedules: ${err.message}`));
    }
  });
program
  .command("serve")
  .description("Start the Fastify application with npm run dev")
  .option("-p, --port <port>", "Port number", "4115")
  .option("--help", "Display help for this command")
  .action((options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'serve':"));
      console.log(
        "  Description: Start the Fastify application with npm run dev"
      );
      console.log("  Usage: serve [-p, --port <port>]");
      console.log("  Options:");
      console.log("    -p, --port <port>  Port number (default: 4115)");
      return;
    }
    try {
      const devProcess = spawn(
        "npm",
        ["run", "dev", "--", `--port=${options.port}`],
        {
          stdio: "inherit",
          shell: true,
        }
      );
      devProcess.on("error", (err) =>
        console.error(chalk.red(`Failed to start npm run dev: ${err.message}`))
      );
      devProcess.on("close", (code) => {
        if (code !== 0) {
          console.error(chalk.red(`npm run dev exited with code ${code}`));
        }
      });
      console.log(
        chalk.green(`Starting npm run dev on port ${options.port}...`)
      );
    } catch (err) {
      console.error(chalk.red(`Error starting serve: ${err.message}`));
    }
  });
program
  .command("schedule:run")
  .description("Run all scheduled tasks once")
  .option("--help", "Display help for this command")
  .action(async (options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'schedule:run':"));
      console.log("  Description: Run all scheduled tasks once");
      console.log("  Usage: schedule:run");
      console.log("  No additional options available.");
      return;
    }
    try {
      const config = await Config.load();
      const app = new Application({
        ...config.app,
        routes: {
          console: (
            await import(
              pathToFileURL(path.join(process.cwd(), "routes/console.js")).href
            )
          ).default,
        },
      });
      app.fastify.config = config;
      app.provider(QueueProvider);
      await app.bootstrap();
      await app.routes.console(app);
      await ScheduleFacade.run();
      console.log(chalk.green("Scheduled tasks started"));
    } catch (err) {
      console.error(chalk.red(`Error running schedules: ${err.message}`));
    }
  });
program
  .command("queue:work")
  .description("Process jobs from the queue")
  .option("--queue <queue>", "Queue to process", "default")
  .option("--tries <tries>", "Number of retries", "3")
  .option("--help", "Display help for this command")
  .action((options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'queue:work':"));
      console.log("  Description: Process jobs from the queue");
      console.log("  Usage: queue:work [--queue <queue>] [--tries <tries>]");
      console.log("  Options:");
      console.log("    --queue <queue>  Queue to process (default: 'default')");
      console.log("    --tries <tries>  Number of retries (default: 3)");
      return;
    }
    try {
      const __filename = fileURLToPath(import.meta.url);
      const __dirname = dirname(__filename);
      const worker = spawn("node", [path.join(__dirname, "worker.js")], {
        stdio: "inherit",
        env: {
          ...process.env,
          QUEUE_NAME: options.queue,
          QUEUE_TRIES: options.tries,
        },
      });
      worker.on("error", (err) =>
        console.error(chalk.red(`Worker error: ${err.message}`))
      );
      console.log(chalk.green(`Processing queue: ${options.queue}`));
    } catch (err) {
      console.error(chalk.red(`Failed to start queue worker: ${err.message}`));
    }
  });
program
  .command("broadcast:test")
  .description("Send a test broadcast message")
  .option("--channel <channel>", "Channel name", "my-channel")
  .option("--event <event>", "Event name", "my-event")
  .option("--data <data>", "Data to send", "Test message")
  .option("--help", "Display help for this command")
  .action(async (options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'broadcast:test':"));
      console.log("  Description: Send a test broadcast message");
      console.log(
        "  Usage: broadcast:test [--channel <channel>] [--event <event>] [--data <data>]"
      );
      console.log("  Options:");
      console.log(
        "    --channel <channel>  Channel name (default: 'my-channel')"
      );
      console.log("    --event <event>      Event name (default: 'my-event')");
      console.log(
        "    --data <data>        Data to send (default: 'Test message')"
      );
      return;
    }
    try {
      const config = await Config.load();
      const app = new Application({
        ...config.app,
        routes: {
          channels: (
            await import(
              pathToFileURL(path.join(process.cwd(), "routes/channels.js")).href
            )
          ).default,
        },
      });
      app.fastify.config = config;
      app.provider(BroadcastProvider);
      await app.bootstrap();
      await app.routes.channels(app);
      await BroadcastFacade.toRoom(
        options.channel,
        options.event,
        options.data
      );
      console.log(
        chalk.green(`Broadcast sent to ${options.channel}: ${options.event}`)
      );
    } catch (err) {
      console.error(chalk.red(`Broadcast failed: ${err.message}`));
    }
  });
program
  .command("make:controller <name>")
  .description("Create a new controller class")
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:controller':"));
      console.log("  Description: Create a new controller class");
      console.log("  Usage: make:controller <name>");
      console.log("  Arguments:");
      console.log("    <name>  Name of the controller class");
      return;
    }
    generateFile(templates.controller, name);
  });
program
  .command("make:model <name>")
  .description("Create a new model class")
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:model':"));
      console.log("  Description: Create a new model class");
      console.log("  Usage: make:model <name>");
      console.log("  Arguments:");
      console.log("    <name>  Name of the model class");
      return;
    }
    generateFile(templates.model, name);
  });
program
  .command("make:middleware <name>")
  .description("Create a new middleware class")
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:middleware':"));
      console.log("  Description: Create a new middleware class");
      console.log("  Usage: make:middleware <name>");
      console.log("  Arguments:");
      console.log("    <name>  Name of the middleware class");
      return;
    }
    generateFile(templates.middleware, name);
  });
program
  .command("make:job <name>")
  .description("Create a new job class")
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:job':"));
      console.log("  Description: Create a new job class");
      console.log("  Usage: make:job <name>");
      console.log("  Arguments:");
      console.log("    <name>  Name of the job class");
      return;
    }
    generateFile(templates.job, name);
  });
program
  .command("make:event <name>")
  .description("Create a new event class")
  .option(
    "--listener <listener>",
    "Add to AppServiceProvider with specified listener"
  )
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:event':"));
      console.log("  Description: Create a new event class");
      console.log("  Usage: make:event <name> [--listener <listener>]");
      console.log("  Arguments:");
      console.log("    <name>  Name of the event class");
      console.log("  Options:");
      console.log(
        "    --listener <listener>  Add to AppServiceProvider with specified listener"
      );
      return;
    }
    generateFile(templates.event, name);
    if (options.listener) {
      updateAppServiceProvider(name, options.listener);
    }
  });
program
  .command("make:listener <name>")
  .description("Create a new listener class")
  .option("--queue", "Add to config/queue.js as handler")
  .option("--event <event>", "Add to AppServiceProvider with specified event")
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:listener':"));
      console.log("  Description: Create a new listener class");
      console.log("  Usage: make:listener <name> [--queue] [--event <event>]");
      console.log("  Arguments:");
      console.log("    <name>  Name of the listener class");
      console.log("  Options:");
      console.log("    --queue          Add to config/queue.js as handler");
      console.log(
        "    --event <event>  Add to AppServiceProvider with specified event"
      );
      return;
    }
    generateFile(templates.listener, name);
    if (options.queue) {
      updateConfig(
        "queue",
        name,
        path.join(process.cwd(), "app/listeners", `${capitalize(name)}.js`)
      );
    }
    if (options.event) {
      updateAppServiceProvider(options.event, name);
    }
  });
program
  .command("make:command <name>")
  .description("Create a new console command")
  .option("--schedule", "Add to config/schedule.js")
  .option("--help", "Display help for this command")
  .action((name, options) => {
    if (options.help) {
      console.log(chalk.cyan("Help for 'make:command':"));
      console.log("  Description: Create a new console command");
      console.log("  Usage: make:command <name> [--schedule]");
      console.log("  Arguments:");
      console.log("    <name>  Name of the command class");
      console.log("  Options:");
      console.log("    --schedule  Add to config/schedule.js");
      return;
    }
    generateFile(templates.command, name);
    if (options.schedule) {
      updateConfig(
        "schedule",
        name,
        path.join(
          process.cwd(),
          "app/console/commands",
          `${capitalize(name)}.js`
        )
      );
    }
  });
loadCommands().then(() => {
  program.parse(process.argv);
  // If no command is provided, show the global help
  if (!process.argv.slice(2).length) {
    program.outputHelp();
  }
});