From 5cc4c5a689e124e3951c80f0cfda0de4a797fec7 Mon Sep 17 00:00:00 2001 From: hachem Date: Tue, 9 Dec 2025 23:02:55 +0100 Subject: [add]: merging and refactor --- README.md | 25 ++- cli/app.js | 138 +++++++++++++++++ cli/commands/check.js | 81 ++++++++++ cli/commands/clean.js | 80 ++++++++++ cli/commands/generate.js | 237 ++++++++++++++++++++++++++++ cli/commands/list.js | 15 ++ cli/commands/merge.js | 156 +++++++++++++++++++ config/config.json | 24 +-- package.json | 5 +- updater/cli.js | 393 ----------------------------------------------- 10 files changed, 736 insertions(+), 418 deletions(-) create mode 100644 cli/app.js create mode 100644 cli/commands/check.js create mode 100644 cli/commands/clean.js create mode 100644 cli/commands/generate.js create mode 100644 cli/commands/list.js create mode 100644 cli/commands/merge.js delete mode 100644 updater/cli.js diff --git a/README.md b/README.md index 95b68ed..2d6698f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ npm install "minecraft_version": "1.21.10", "fabric_loader_version": "0.17.3", "loader_meta_version": "1.1.0", + "java_path": null, + "server_location": "C:/path/to/your/server", + "backup_location": "C:/path/to/backups", "modrinth_mods": [ "fabric-api", "lithium", @@ -53,10 +56,10 @@ npm run benson ``` ╔══════════════════════════════════════╗ ║ BENSON CLI v1.0 ║ - ║ Fabric Mod Manager for MC 1.21.10 ║ + ║ Fabric Server for MC 1.21.10 ║ ╚══════════════════════════════════════╝ - Type 'help' for available commands. + Type 'help' for commands. benson> _ ``` @@ -66,10 +69,12 @@ benson> _ | Command | Description | |---------|-------------| | `list-mods` | List configured mods | +| `check` | Check if all mods are compatible before generating | | `generate` | Download mods + server, run to generate files | -| `merge` | (coming soon) | -| `clean generated` | Remove generated server files | -| `clean all` | (coming soon) | +| `merge` | Backup server, move generated files to server location | +| `clean generated` | Remove server directory | +| `clean latest` | Remove latest backup zip | +| `clean all` | Remove both generated and latest backup | | `help` | Show available commands | | `exit` | Exit the CLI | @@ -82,8 +87,14 @@ Benson/ │ └── config.js # Shared config/cache utilities ├── bot/ │ └── app.js # Discord bot -├── updater/ -│ └── cli.js # Interactive CLI +├── cli/ +│ ├── app.js # CLI entry point +│ └── commands/ +│ ├── check.js # Check command +│ ├── clean.js # Clean commands +│ ├── generate.js # Generate command +│ ├── list.js # List-mods command +│ └── merge.js # Merge command ├── config/ │ ├── config.json # Main configuration │ └── api.json # Discord token (not in repo) diff --git a/cli/app.js b/cli/app.js new file mode 100644 index 0000000..1252358 --- /dev/null +++ b/cli/app.js @@ -0,0 +1,138 @@ +#!/usr/bin/env node + +const readline = require("readline") + +const { LoadConfig } = require("../lib/config") + +const { Check } = require("./commands/check") +const { Generate } = require("./commands/generate") +const { Merge } = require("./commands/merge") +const { CleanGenerated, CleanLatest, CleanAll } = require("./commands/clean") +const { ListMods } = require("./commands/list") + +const pkg = require("../package.json") +const config = LoadConfig() +const { minecraft_version } = config + +const ShowHelp = () => +{ + console.log(` + Commands: + + list-mods List configured mods + check Check if all mods are compatible before generating + generate Download mods + server, run to generate files + merge Backup server, move generated files to server location + clean generated Remove server directory + clean latest Remove latest backup zip + clean all Remove both generated and latest backup + help Show this help + exit Exit +`) +} + +const ProcessCommand = async (input, rl) => +{ + const parts = input.trim().toLowerCase().split(/\s+/) + const cmd = parts[0] + const arg = parts[1] + + switch (cmd) + { + case "list-mods": + ListMods() + break + + case "check": + await Check() + break + + case "generate": + await Generate(rl) + break + + case "merge": + await Merge() + break + + case "clean": + if (arg === "generated") + await CleanGenerated() + else if (arg === "latest") + await CleanLatest() + else if (arg === "all") + await CleanAll() + else + console.log(" Usage: clean generated | clean latest | clean all") + break + + case "help": + case "?": + ShowHelp() + break + + case "exit": + case "quit": + case "q": + console.log(" Goodbye!\n") + process.exit(0) + + case "": + break + + default: + console.log(` Unknown command: ${cmd}. Type 'help' for commands.`) + } +} + +const DrawBox = (lines) => +{ + const maxLen = Math.max(...lines.map(l => l.length)) + const pad = (str) => str + " ".repeat(maxLen - str.length) + + const top = "╔" + "═".repeat(maxLen + 2) + "╗" + const bot = "╚" + "═".repeat(maxLen + 2) + "╝" + const mid = lines.map(l => "║ " + pad(l) + " ║").join("\n ") + + return ` ${top}\n ${mid}\n ${bot}` +} + +const Main = () => +{ + const title = `BENSON CLI v${pkg.version}` + const subtitle = `Fabric Server for MC ${minecraft_version}` + + const box = DrawBox([title, subtitle]) + + console.log(` +${box} + + Type 'help' for commands. +`) + + const rl = readline.createInterface + ({ + input: process.stdin, + output: process.stdout + }) + + const Prompt = () => + { + rl.question("benson> ", async (input) => + { + await ProcessCommand(input, rl) + Prompt() + }) + } + + rl.on("close", () => + { + console.log("\n Goodbye!\n") + process.exit(0) + }) + + Prompt() +} + +Main() + diff --git a/cli/commands/check.js b/cli/commands/check.js new file mode 100644 index 0000000..f91b710 --- /dev/null +++ b/cli/commands/check.js @@ -0,0 +1,81 @@ +const { LoadConfig } = require("../../lib/config") +const { GetModVersions, GetCompatibleVersion } = require("../../lib/modrinth") + +const config = LoadConfig() +const { minecraft_version, modrinth_mods } = config + +const Check = async () => +{ + console.log(`\n Checking mod compatibility for MC ${minecraft_version} (Fabric)...\n`) + + const compatible = [] + const incompatible = [] + const failed = [] + + for (const slug of modrinth_mods) + { + try + { + const versions = await GetModVersions(slug) + if (!versions) + { + failed.push(slug) + continue + } + + const match = GetCompatibleVersion(versions, minecraft_version, "fabric") + if (match) + { + const filename = match.files[0]?.filename || match.version_number + compatible.push({ slug, version: filename }) + } + else + { + incompatible.push(slug) + } + } + catch (err) + { + failed.push(slug) + } + } + + // Display results + if (compatible.length) + { + console.log(" [OK] Compatible:") + for (const { slug, version } of compatible) + console.log(` ${slug} -> ${version}`) + console.log("") + } + + if (incompatible.length) + { + console.log(" [FAIL] No compatible version:") + for (const slug of incompatible) + console.log(` ${slug}`) + console.log("") + } + + if (failed.length) + { + console.log(" [WARN] Failed to check:") + for (const slug of failed) + console.log(` ${slug}`) + console.log("") + } + + // Summary + const total = modrinth_mods.length + const ok = compatible.length + + console.log(` Summary: ${ok}/${total} mods compatible\n`) + + if (incompatible.length === 0 && failed.length === 0) + console.log(" [OK] Safe to generate!\n") + else + console.log(" [FAIL] Not all mods are compatible. Generate may have issues.\n") +} + +module.exports = { Check } + diff --git a/cli/commands/clean.js b/cli/commands/clean.js new file mode 100644 index 0000000..99a96f6 --- /dev/null +++ b/cli/commands/clean.js @@ -0,0 +1,80 @@ +const fs = require("fs-extra") +const path = require("path") + +const { LoadConfig } = require("../../lib/config") +const { serverDir } = require("./generate") + +const config = LoadConfig() +const { backup_location } = config + +const CleanGenerated = async () => +{ + try + { + if (await fs.pathExists(serverDir)) + { + await fs.remove(serverDir) + console.log(" Removed server directory.") + } + else + { + console.log(" Server directory doesn't exist.") + } + } + catch (err) + { + console.error(" Failed to remove server directory:", err.message) + } +} + +const CleanLatest = async () => +{ + if (!backup_location) + { + console.log(" Error: backup_location must be set in config.") + return + } + + if (!await fs.pathExists(backup_location)) + { + console.log(" Backup directory doesn't exist.") + return + } + + try + { + const files = await fs.readdir(backup_location) + const zipFiles = files + .filter(f => f.startsWith("backup_") && f.endsWith(".zip")) + .sort() + .reverse() + + if (zipFiles.length === 0) + { + console.log(" No backup files found.") + return + } + + const latestZip = zipFiles[0] + const latestPath = path.join(backup_location, latestZip) + + await fs.remove(latestPath) + console.log(` Removed latest backup: ${latestZip}`) + } + catch (err) + { + console.error(" Failed to remove latest backup:", err.message) + } +} + +const CleanAll = async () => +{ + console.log(" Cleaning generated files...") + await CleanGenerated() + console.log("") + console.log(" Cleaning latest backup...") + await CleanLatest() +} + +module.exports = { CleanGenerated, CleanLatest, CleanAll } + diff --git a/cli/commands/generate.js b/cli/commands/generate.js new file mode 100644 index 0000000..183810d --- /dev/null +++ b/cli/commands/generate.js @@ -0,0 +1,237 @@ +const fs = require("fs-extra") +const path = require("path") +const { spawn } = require("child_process") + +const { LoadConfig } = require("../../lib/config") +const { GetModVersions, GetCompatibleVersion, DownloadFile } = require("../../lib/modrinth") + +const config = LoadConfig() + +const +{ + minecraft_version, + fabric_loader_version, + loader_meta_version, + java_path, + modrinth_mods +} = config + +const javaCmd = java_path || "java" + +const serverDir = path.join(__dirname, "../../server") +const modsDir = path.join(serverDir, "mods") + +const deleteList = +[ + "audioplayer_uploads", + "config", + "cache", + "world", + "logs", + "banned-players.json", + "server.properties", + "banned-ips.json", + "usercache.json", + "whitelist.json", + "ops.json", + "eula.txt" +] + +const EnsureDirs = async () => +{ + await fs.ensureDir(serverDir) + await fs.ensureDir(modsDir) +} + +const DownloadModrinthMods = async (modList, mcVersion) => +{ + const downloadedFiles = [] + const failedMods = [] + + for (const slug of modList) + { + try + { + const versions = await GetModVersions(slug) + if (!versions) + { + console.log(` [Modrinth] Could not get versions for ${slug}`) + failedMods.push(slug) + continue + } + + const compatible = GetCompatibleVersion(versions, mcVersion, "fabric") + if (!compatible) + { + console.log(` [Modrinth] No Fabric version for ${slug} on MC ${mcVersion}`) + failedMods.push(slug) + continue + } + + const fileInfo = compatible.files[0] + const jarUrl = fileInfo.url + const filename = fileInfo.filename + const destPath = path.join(modsDir, filename) + + console.log(` [Modrinth] Downloading ${slug} -> ${filename} ...`) + + const success = await DownloadFile(jarUrl, destPath) + if (!success) + { + console.log(` [Modrinth] Couldn't download jar for ${slug}`) + failedMods.push(slug) + continue + } + + downloadedFiles.push(destPath) + console.log(` [Modrinth] Downloaded: ${filename}`) + } + catch (err) + { + console.error(` [Modrinth] Error downloading ${slug}:`, err.message) + failedMods.push(slug) + } + } + + return { downloadedFiles, failedMods } +} + +const DownloadFabricServer = async (mcVersion, loaderVersion, metaVersion) => +{ + const url = `https://meta.fabricmc.net/v2/versions/loader/${mcVersion}/${loaderVersion}/${metaVersion}/server/jar` + const filename = "server.jar" + const destPath = path.join(serverDir, filename) + + console.log(` [Fabric] Downloading Fabric server for MC ${mcVersion} ...`) + + const success = await DownloadFile(url, destPath) + if (!success) + { + console.log(` [Fabric] Failed to download Fabric server JAR`) + return null + } + + console.log(` [Fabric] Downloaded: ${filename}`) + return destPath +} + +const RunServerAutoStop = (rl) => +{ + return new Promise((resolve) => + { + const jarPath = path.join(serverDir, "server.jar") + + if (!fs.existsSync(jarPath)) + { + console.log(" Server JAR not found.") + resolve(false) + return + } + + const eulaPath = path.join(serverDir, "eula.txt") + if (!fs.existsSync(eulaPath)) + { + console.log(" Accepting EULA...") + fs.writeFileSync(eulaPath, "eula=true\n") + } + + console.log(" Starting server (will auto-stop after loading)...\n") + + rl.pause() + + let hadError = false + + const serverProcess = spawn(javaCmd, ["-jar", "server.jar", "nogui"], + { + cwd: serverDir, + stdio: ["pipe", "pipe", "pipe"] + }) + + serverProcess.stdout.on("data", (data) => + { + const line = data.toString() + process.stdout.write(line) + + if (line.includes("Done")) + { + console.log("\n Server loaded, stopping...") + serverProcess.stdin.write("stop\n") + } + }) + + serverProcess.stderr.on("data", (data) => + { + process.stderr.write(data.toString()) + }) + + serverProcess.on("error", err => + { + hadError = true + console.error(" Failed to start server:", err.message) + rl.resume() + resolve(false) + }) + + serverProcess.on("close", () => + { + if (!hadError) + { + console.log("\n Server stopped.") + rl.resume() + resolve(true) + } + }) + }) +} + +const DeletePostGenerate = async () => +{ + console.log(" Cleaning up server-generated files...") + + let removed = 0 + for (const item of deleteList) + { + const itemPath = path.join(serverDir, item) + + try + { + if (await fs.pathExists(itemPath)) + { + await fs.remove(itemPath) + removed++ + } + } + catch (err) + { + console.error(` Failed to remove ${item}:`, err.message) + } + } + + console.log(` Removed ${removed} items.`) +} + +const Generate = async (rl) => +{ + await EnsureDirs() + + console.log(`\n Generating server for MC ${minecraft_version}...\n`) + + const { downloadedFiles, failedMods } = await DownloadModrinthMods(modrinth_mods, minecraft_version) + console.log(`\n [OK] Downloaded ${downloadedFiles.length} mods`) + if (failedMods.length) + console.log(` [FAIL] Failed: ${failedMods.join(", ")}`) + + const serverFile = await DownloadFabricServer(minecraft_version, fabric_loader_version, loader_meta_version) + + if (serverFile) + { + console.log("") + await RunServerAutoStop(rl) + await DeletePostGenerate() + } + + console.log("") +} + +module.exports = { Generate, serverDir } + diff --git a/cli/commands/list.js b/cli/commands/list.js new file mode 100644 index 0000000..48bb2cf --- /dev/null +++ b/cli/commands/list.js @@ -0,0 +1,15 @@ +const { LoadConfig } = require("../../lib/config") + +const config = LoadConfig() +const { minecraft_version, modrinth_mods } = config + +const ListMods = () => +{ + console.log(`\n Mods for MC ${minecraft_version}:\n`) + for (const slug of modrinth_mods) + console.log(` - ${slug}`) + console.log(`\n Total: ${modrinth_mods.length} mods\n`) +} + +module.exports = { ListMods } + diff --git a/cli/commands/merge.js b/cli/commands/merge.js new file mode 100644 index 0000000..138dd54 --- /dev/null +++ b/cli/commands/merge.js @@ -0,0 +1,156 @@ +const fs = require("fs-extra") +const path = require("path") +const { spawn } = require("child_process") +const archiver = require("archiver") + +const { LoadConfig } = require("../../lib/config") +const { serverDir } = require("./generate") + +const config = LoadConfig() + +const +{ + server_location, + backup_location, + merge_pre_command, + merge_post_command +} = config + +const RunCommand = (command) => +{ + return new Promise((resolve) => + { + console.log(` Running: ${command}`) + + const proc = spawn(command, [], { shell: true, stdio: "inherit" }) + + proc.on("close", (code) => + { + if (code === 0) + console.log(" Command completed.") + else + console.log(` Command exited with code ${code}`) + resolve(code) + }) + + proc.on("error", (err) => + { + console.error(" Command failed:", err.message) + resolve(1) + }) + }) +} + +const CreateBackup = (sourceDir, backupDir) => +{ + return new Promise(async (resolve, reject) => + { + const now = new Date() + const timestamp = now.toISOString() + .replace(/[:.]/g, "-") + .replace("T", "_") + .slice(0, 19) + + const zipName = `backup_${timestamp}.zip` + const zipPath = path.join(backupDir, zipName) + + await fs.ensureDir(backupDir) + + const output = fs.createWriteStream(zipPath) + const archive = archiver("zip", { zlib: { level: 9 } }) + + output.on("close", () => + { + console.log(` Created backup: ${zipName} (${archive.pointer()} bytes)`) + resolve(zipPath) + }) + + archive.on("error", err => reject(err)) + + archive.pipe(output) + archive.directory(sourceDir, false) + archive.finalize() + }) +} + +const Merge = async () => +{ + if (!server_location || !backup_location) + { + console.log(" Error: server_location and backup_location must be set in config.") + return + } + + if (!await fs.pathExists(serverDir)) + { + console.log(" Error: No generated files found. Run 'generate' first.") + return + } + + // Run pre-command + if (merge_pre_command) + { + console.log("\n Running pre-merge command...") + await RunCommand(merge_pre_command) + console.log("") + } + + // Check if server location exists and back it up + if (await fs.pathExists(server_location)) + { + console.log(` Backing up ${server_location}...`) + try + { + await CreateBackup(server_location, backup_location) + } + catch (err) + { + console.error(" Failed to create backup:", err.message) + return + } + } + else + { + console.log(" Server location doesn't exist, creating it...") + await fs.ensureDir(server_location) + } + + // Move generated files to server location + console.log(" Moving generated files to server location...") + try + { + const items = await fs.readdir(serverDir) + for (const item of items) + { + const src = path.join(serverDir, item) + const dest = path.join(server_location, item) + + // Remove existing item at destination + if (await fs.pathExists(dest)) + await fs.remove(dest) + + await fs.move(src, dest) + console.log(` Moved: ${item}`) + } + + // Remove the now-empty server directory + await fs.remove(serverDir) + + console.log(" Merge complete!") + } + catch (err) + { + console.error(" Failed to move files:", err.message) + return + } + + // Run post-command + if (merge_post_command) + { + console.log("\n Running post-merge command...") + await RunCommand(merge_post_command) + } +} + +module.exports = { Merge } + diff --git a/config/config.json b/config/config.json index 67011f9..e7c0844 100644 --- a/config/config.json +++ b/config/config.json @@ -4,20 +4,12 @@ "minecraft_version": "1.21.10", "fabric_loader_version": "0.17.3", "loader_meta_version": "1.1.0", - "java_path": "C:/Program Files/Java/jdk-25/bin/java.exe", + "java_path": null, - "modrinth_mods": [ - "audioplayer", - "c2me-fabric", - "convenient-mobgriefing", - "fabric-api", - "image2map", - "lithium", - "simple-voice-chat", - "cloth-config", - "ferrite-core", - "player-roles", - "distanthorizons", - "spawn-radar" - ] -} \ No newline at end of file + "server_location": null, + "backup_location": null, + "merge_pre_command": null, + "merge_post_command": null, + + "modrinth_mods": [ ] +} diff --git a/package.json b/package.json index 560bb6a..ad276ad 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,15 @@ "main": "bot/app.js", "scripts": { "bot": "node bot/app.js", - "benson": "node updater/cli.js" + "benson": "node cli/app.js" }, "dependencies": { + "archiver": "^7.0.1", "axios": "^1.12.2", "discord.js": "^14.23.2", "fs-extra": "^11.3.2" }, "bin": { - "benson": "./updater/cli.js" + "benson": "./cli/app.js" } } diff --git a/updater/cli.js b/updater/cli.js deleted file mode 100644 index 8731ab6..0000000 --- a/updater/cli.js +++ /dev/null @@ -1,393 +0,0 @@ -#!/usr/bin/env node - -const readline = require("readline") -const fs = require("fs-extra") -const path = require("path") -const { spawn } = require("child_process") - -const { LoadConfig } = require("../lib/config") -const { GetModVersions, GetCompatibleVersion, DownloadFile } = require("../lib/modrinth") - -const pkg = require("../package.json") -const config = LoadConfig() - -const -{ - minecraft_version, - fabric_loader_version, - loader_meta_version, - java_path, - modrinth_mods -} = config - -const javaCmd = java_path || "java" - -const serverDir = path.join(__dirname, "../server") -const modsDir = path.join(serverDir, "mods") - -const deleteList = -[ - "audioplayer_uploads", - "config", - "cache", - "world", - "logs", - "banned-players.json", - "server.properties", - "banned-ips.json", - "usercache.json", - "whitelist.json", - "ops.json", - "eula.txt" -] - - -const EnsureDirs = async () => -{ - await fs.ensureDir(serverDir) - await fs.ensureDir(modsDir) -} - -const DownloadModrinthMods = async (modList, mcVersion) => -{ - const downloadedFiles = [] - const failedMods = [] - - for (const slug of modList) - { - try - { - const versions = await GetModVersions(slug) - if (!versions) - { - console.log(` [Modrinth] Could not get versions for ${slug}`) - failedMods.push(slug) - continue - } - - const compatible = GetCompatibleVersion(versions, mcVersion, "fabric") - if (!compatible) - { - console.log(` [Modrinth] No Fabric version for ${slug} on MC ${mcVersion}`) - failedMods.push(slug) - continue - } - - const fileInfo = compatible.files[0] - const jarUrl = fileInfo.url - const filename = fileInfo.filename - const destPath = path.join(modsDir, filename) - - console.log(` [Modrinth] Downloading ${slug} -> ${filename} ...`) - - const success = await DownloadFile(jarUrl, destPath) - if (!success) - { - console.log(` [Modrinth] Couldn't download jar for ${slug}`) - failedMods.push(slug) - continue - } - - downloadedFiles.push(destPath) - console.log(` [Modrinth] Downloaded: ${filename}`) - } - catch (err) - { - console.error(` [Modrinth] Error downloading ${slug}:`, err.message) - failedMods.push(slug) - } - } - - return { downloadedFiles, failedMods } -} - -const DownloadFabricServer = async (mcVersion, loaderVersion, metaVersion) => -{ - const url = `https://meta.fabricmc.net/v2/versions/loader/${mcVersion}/${loaderVersion}/${metaVersion}/server/jar` - const filename = "server.jar" - const destPath = path.join(serverDir, filename) - - console.log(` [Fabric] Downloading Fabric server for MC ${mcVersion} ...`) - - const success = await DownloadFile(url, destPath) - if (!success) - { - console.log(` [Fabric] Failed to download Fabric server JAR`) - return null - } - - console.log(` [Fabric] Downloaded: ${filename}`) - return destPath -} - -const RunServerAutoStop = (rl) => -{ - return new Promise((resolve) => - { - const jarPath = path.join(serverDir, "server.jar") - - if (!fs.existsSync(jarPath)) - { - console.log(" Server JAR not found.") - resolve(false) - return - } - - const eulaPath = path.join(serverDir, "eula.txt") - if (!fs.existsSync(eulaPath)) - { - console.log(" Accepting EULA...") - fs.writeFileSync(eulaPath, "eula=true\n") - } - - console.log(" Starting server (will auto-stop after loading)...\n") - - rl.pause() - - let hadError = false - - const serverProcess = spawn(javaCmd, ["-jar", "server.jar", "nogui"], - { - cwd: serverDir, - stdio: ["pipe", "pipe", "pipe"] - }) - - serverProcess.stdout.on("data", (data) => - { - const line = data.toString() - process.stdout.write(line) - - if (line.includes("Done")) - { - console.log("\n Server loaded, stopping...") - serverProcess.stdin.write("stop\n") - } - }) - - serverProcess.stderr.on("data", (data) => - { - process.stderr.write(data.toString()) - }) - - serverProcess.on("error", err => - { - hadError = true - console.error(" Failed to start server:", err.message) - rl.resume() - resolve(false) - }) - - serverProcess.on("close", () => - { - if (!hadError) - { - console.log("\n Server stopped.") - rl.resume() - resolve(true) - } - }) - }) -} - -const DeletePostGenerate = async () => -{ - console.log(" Cleaning up server-generated files...") - - let removed = 0 - for (const item of deleteList) - { - const itemPath = path.join(serverDir, item) - - try - { - if (await fs.pathExists(itemPath)) - { - await fs.remove(itemPath) - removed++ - } - } - catch (err) - { - console.error(` Failed to remove ${item}:`, err.message) - } - } - - console.log(` Removed ${removed} items.`) -} - -const CleanGenerated = async () => -{ - try - { - if (await fs.pathExists(serverDir)) - { - await fs.remove(serverDir) - console.log(" Removed server directory.") - } - else - { - console.log(" Server directory doesn't exist.") - } - } - catch (err) - { - console.error(" Failed to remove server directory:", err.message) - } -} - -const CleanAll = async () => -{ - // Placeholder - will be implemented later - console.log(" clean all - not implemented yet") -} - -const ListMods = () => -{ - console.log(`\n Mods for MC ${minecraft_version}:\n`) - for (const slug of modrinth_mods) - console.log(` - ${slug}`) - console.log(`\n Total: ${modrinth_mods.length} mods\n`) -} - -const Generate = async (rl) => -{ - await EnsureDirs() - - console.log(`\n Generating server for MC ${minecraft_version}...\n`) - - const { downloadedFiles, failedMods } = await DownloadModrinthMods(modrinth_mods, minecraft_version) - console.log(`\n ✅ Downloaded ${downloadedFiles.length} mods`) - if (failedMods.length) - console.log(` ❌ Failed: ${failedMods.join(", ")}`) - - const serverFile = await DownloadFabricServer(minecraft_version, fabric_loader_version, loader_meta_version) - - if (serverFile) - { - console.log("") - await RunServerAutoStop(rl) - await DeletePostGenerate() - } - - console.log("") -} - -const Merge = async () => -{ - // Placeholder - will be implemented later - console.log(" merge - not implemented yet") -} - -const ShowHelp = () => -{ - console.log(` - Commands: - - list-mods List configured mods - generate Download mods + server, run to generate files - merge (coming soon) - clean generated Remove generated server files - clean all (coming soon) - help Show this help - exit Exit -`) -} - -const ProcessCommand = async (input, rl) => -{ - const parts = input.trim().toLowerCase().split(/\s+/) - const cmd = parts[0] - const arg = parts[1] - - switch (cmd) - { - case "list-mods": - ListMods() - break - - case "generate": - await Generate(rl) - break - - case "merge": - await Merge() - break - - case "clean": - if (arg === "generated") - await CleanGenerated() - else if (arg === "all") - await CleanAll() - else - console.log(" Usage: clean generated | clean all") - break - - case "help": - case "?": - ShowHelp() - break - - case "exit": - case "quit": - case "q": - console.log(" Goodbye!\n") - process.exit(0) - - case "": - break - - default: - console.log(` Unknown command: ${cmd}. Type 'help' for commands.`) - } -} - -const DrawBox = (lines) => -{ - const maxLen = Math.max(...lines.map(l => l.length)) - const pad = (str) => str + " ".repeat(maxLen - str.length) - - const top = "╔" + "═".repeat(maxLen + 2) + "╗" - const bot = "╚" + "═".repeat(maxLen + 2) + "╝" - const mid = lines.map(l => "║ " + pad(l) + " ║").join("\n ") - - return ` ${top}\n ${mid}\n ${bot}` -} - -const Main = () => -{ - const title = `BENSON CLI v${pkg.version}` - const subtitle = `Fabric Server for MC ${minecraft_version}` - - const box = DrawBox([title, subtitle]) - - console.log(` -${box} - - Type 'help' for commands. -`) - - const rl = readline.createInterface - ({ - input: process.stdin, - output: process.stdout - }) - - const Prompt = () => - { - rl.question("benson> ", async (input) => - { - await ProcessCommand(input, rl) - Prompt() - }) - } - - rl.on("close", () => - { - console.log("\n Goodbye!\n") - process.exit(0) - }) - - Prompt() -} - -Main() -- cgit v1.3