From 924b67f456b87df9d96ab583ec3c5d039b662bc7 Mon Sep 17 00:00:00 2001 From: The-Best-Codes Date: Tue, 7 Jan 2025 23:22:54 -0600 Subject: [PATCH] add dev command --- bun.lock | 9 +- package.json | 5 +- package/src/cli.ts | 25 ++++++ package/src/cli/build/build.ts | 13 ++- package/src/cli/build/index.ts | 1 + package/src/cli/dev/index.ts | 156 +++++++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 package/src/cli/dev/index.ts diff --git a/bun.lock b/bun.lock index 3da7d61..b557ce3 100755 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,7 @@ "workspaces": { "": { "dependencies": { + "chokidar": "latest", "commander": "latest", "consola": "latest", "esbuild": "latest", @@ -159,7 +160,7 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.1.42", "", { "os": "win32", "cpu": "x64" }, "sha512-xnlYa1jKknImCw7xmSD91H8e+w3BC6mIShOfHhFWfNhdyvEtundXhIu7VddwxKBMs5S/iiFJiutnZ2EyLq4CAQ=="], - "@types/bun": ["@types/bun@1.1.14", "", { "dependencies": { "bun-types": "1.1.37" } }, "sha512-opVYiFGtO2af0dnWBdZWlioLBoxSdDO5qokaazLhq8XQtGZbY4pY3/JxY8Zdf/hEwGubbp7ErZXoN1+h2yesxA=="], + "@types/bun": ["@types/bun@1.1.15", "", { "dependencies": { "bun-types": "1.1.42" } }, "sha512-Fi7ND1jCq8O5iU3s9z3TKHggD0hidgpe7wSxyisviXpbMmY4B1KiokF3f/mmjOoDrEcf873tSpixgen7Wm9X0g=="], "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], @@ -211,7 +212,7 @@ "bun": ["bun@1.1.42", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.1.42", "@oven/bun-darwin-x64": "1.1.42", "@oven/bun-darwin-x64-baseline": "1.1.42", "@oven/bun-linux-aarch64": "1.1.42", "@oven/bun-linux-aarch64-musl": "1.1.42", "@oven/bun-linux-x64": "1.1.42", "@oven/bun-linux-x64-baseline": "1.1.42", "@oven/bun-linux-x64-musl": "1.1.42", "@oven/bun-linux-x64-musl-baseline": "1.1.42", "@oven/bun-windows-x64": "1.1.42", "@oven/bun-windows-x64-baseline": "1.1.42" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bun.exe" } }, "sha512-PckeNolMEBaBEzixTMvp0jJD9r/9lly8AfctILi1ve14zwwChFjsxI4TJLQO2yezzOjVeG0u7xf8WQFbS7GjAA=="], - "bun-types": ["bun-types@1.1.37", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-C65lv6eBr3LPJWFZ2gswyrGZ82ljnH8flVE03xeXxKhi2ZGtFiO4isRKTKnitbSqtRAcaqYSR6djt1whI66AbA=="], + "bun-types": ["bun-types@1.1.42", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-beMbnFqWbbBQHll/bn3phSwmoOQmnX2nt8NI9iOQKFbgR5Z6rlH3YuaMdlid8vp5XGct3/W4QVQBmhoOEoe4nw=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], @@ -219,6 +220,8 @@ "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -385,6 +388,8 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "readdirp": ["readdirp@4.0.2", "", {}, "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="], diff --git a/package.json b/package.json index d3e225c..c94f77f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discraft", - "version": "1.6.2-beta.11", + "version": "1.6.2-beta.12", "description": "Ultimate Discord bot framework", "type": "module", "packageManager": "bun@1.1.42", @@ -48,7 +48,7 @@ "homepage": "https://github.com/The-Best-Codes/discraft-js#readme", "devDependencies": { "@eslint/js": "^9.17.0", - "@types/bun": "^1.1.14", + "@types/bun": "^1.1.15", "@types/fs-extra": "^11.0.4", "bun": "^1.1.42", "eslint": "^9.17.0", @@ -59,6 +59,7 @@ "typescript": "^5.7.2" }, "dependencies": { + "chokidar": "^4.0.3", "commander": "^13.0.0", "consola": "^3.3.3", "esbuild": "^0.24.2", diff --git a/package/src/cli.ts b/package/src/cli.ts index 275bae5..fb5cba8 100644 --- a/package/src/cli.ts +++ b/package/src/cli.ts @@ -5,6 +5,7 @@ import { program } from "commander"; import consola from "consola"; import { version } from "../../package.json"; import { build } from "./cli/build"; +import { dev } from "./cli/dev"; import { init } from "./cli/init"; import { start } from "./cli/start"; @@ -40,6 +41,30 @@ program }); }); +program + .command("dev") + .description("Start the bot in development mode") + .option( + "-b, --builder ", + "Specify the builder to use (esbuild or bun). Defaults to auto-detect.", + (value) => { + if (value !== "esbuild" && value !== "bun") { + consola.error("Invalid builder value. Must be 'esbuild' or 'bun'."); + process.exit(1); + } + return value; + }, + ) + .option("-c, --clear-console", "Clear the console on each rebuild.", false) + .action((options) => { + dev({ builder: options.builder, clearConsole: options.clearConsole }).catch( + (error) => { + consola.error("An error occurred during development:", error); + process.exit(1); + }, + ); + }); + program .command("init") .description("Initialize a new Discraft project") diff --git a/package/src/cli/build/build.ts b/package/src/cli/build/build.ts index efe7888..aa677b6 100644 --- a/package/src/cli/build/build.ts +++ b/package/src/cli/build/build.ts @@ -1,9 +1,12 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { spawn } from "child_process"; import consola from "consola"; import { build as esbuild } from "esbuild"; import { nodeExternalsPlugin } from "esbuild-node-externals"; import { promisify } from "util"; +// eslint-disable-next-line @typescript-eslint/no-require-imports const exec = promisify(require("child_process").exec); type Builder = "esbuild" | "bun"; @@ -71,7 +74,10 @@ async function build( }); } catch (error: any) { consola.error(`Error during bun build: ${error.message}`); - process.exit(1); + if (env !== "dev") { + process.exit(1); + } + throw error; // Throw the error, to be caught by `rebuildBot` } } else { try { @@ -87,7 +93,10 @@ async function build( consola.success("Build complete using esbuild."); } catch (error: any) { consola.error(`Error during esbuild: ${error.message}`); - process.exit(1); + if (env !== "dev") { + process.exit(1); + } + throw error; // Throw the error, to be caught by `rebuildBot` } } } diff --git a/package/src/cli/build/index.ts b/package/src/cli/build/index.ts index ffaebc2..63e3009 100644 --- a/package/src/cli/build/index.ts +++ b/package/src/cli/build/index.ts @@ -30,6 +30,7 @@ async function startBuild(options?: BuildOptions) { await build("prod", srcPath, outputPath, options?.builder); consola.info("Build output: " + outputPath); consola.success(`Build finished successfully!`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { consola.error(`Build failed: ${error.message}`); } diff --git a/package/src/cli/dev/index.ts b/package/src/cli/dev/index.ts new file mode 100644 index 0000000..06c524b --- /dev/null +++ b/package/src/cli/dev/index.ts @@ -0,0 +1,156 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { spawn } from "child_process"; +import chokidar from "chokidar"; +import consola from "consola"; +import path from "path"; +import { promisify } from "util"; +import { build } from "../build/build"; +import { generateIndexFiles } from "../build/commandsAndEvents"; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const exec = promisify(require("child_process").exec); + +type Builder = "esbuild" | "bun"; + +interface DevOptions { + builder?: Builder; + clearConsole?: boolean; +} + +const CWD = process.cwd(); +const DIST_DIR = path.join(CWD, "dist"); +const DISCRAFT_DIR = path.join(CWD, ".discraft"); +const NODE_MODULES_DIR = path.join(CWD, "node_modules"); +const IGNORED_FILES = [ + DIST_DIR, + DISCRAFT_DIR, + NODE_MODULES_DIR, + "*.log", + ".git", + ".env", +]; + +async function startDev(options?: DevOptions) { + let botProcess: any = null; + let runner: string = "node"; + const clearConsole = options?.clearConsole ?? false; + + // Function to start bot + const startBot = async () => { + const distPath = path.join(DIST_DIR, "index.js"); + + if (botProcess) { + consola.info("🔄 Restarting bot..."); + botProcess.kill("SIGINT"); + await new Promise((resolve) => botProcess.on("exit", resolve)); + consola.info("Old bot process terminated."); + } + + try { + botProcess = spawn(runner, [distPath], { + stdio: "inherit", + }); + + botProcess.on("error", (error: any) => { + consola.error(`Error starting the bot: ${error.message}`); + }); + + botProcess.on("exit", (code: number | null) => { + if (code === 0) { + consola.info("Bot process exited."); + } else { + consola.error(`Bot process exited with code ${code}`); + } + }); + + consola.info("Listening for bot output..."); + } catch (error: any) { + consola.error(`Error starting the bot: ${error.message}`); + } + }; + + // Function to rebuild the bot + const rebuildBot = async () => { + if (clearConsole) { + process.stdout.write("\x1bc"); + } + + consola.info("Change detected, rebuilding..."); + try { + await generateIndexFiles(); + await build( + "dev", + path.join(CWD, "index.ts"), + DIST_DIR, + options?.builder, + ); + consola.success("Rebuild complete."); + await startBot(); + } catch (error: any) { + consola.error(`Rebuild failed: ${error.message}`); + consola.info("Waiting for changes..."); + // Do NOT exit here + } + }; + + // Setup file watching and initial build + try { + consola.info("Starting in development mode..."); + consola.info("Performing initial build..."); + + if (!options?.builder) { + try { + // Use bun --version to check for bun existence + await exec("bun --version"); + runner = "bun"; + consola.info("Bun detected. Using Bun CLI for dev."); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + consola.info("Bun not detected. Using Node CLI for dev."); + } + } else { + runner = options.builder; + consola.info(`Using ${options.builder} instead of auto-detect.`); + } + + await generateIndexFiles(); + await build("dev", path.join(CWD, "index.ts"), DIST_DIR, options?.builder); + consola.success("Initial build complete."); + await startBot(); + + consola.info("Starting file watcher..."); + const watcher = chokidar.watch(CWD, { + ignored: IGNORED_FILES, + ignoreInitial: true, + }); + + watcher.on("all", async (event, path) => { + consola.verbose(`File system event ${event} detected at ${path}`); + await rebuildBot(); + }); + + process.on("SIGINT", () => { + consola.info("SIGINT signal received. Exiting development mode..."); + if (botProcess) { + botProcess.kill("SIGINT"); + } + watcher.close(); + process.exit(0); + }); + + process.on("SIGTERM", () => { + consola.info("SIGTERM signal received. Exiting development mode..."); + if (botProcess) { + botProcess.kill("SIGTERM"); + } + watcher.close(); + process.exit(0); + }); + consola.success("Development environment is ready."); + } catch (error: any) { + consola.error(`Error during setup: ${error.message}`); + process.exit(1); + } +} + +export { startDev as dev };