dressed
Version:
A sleek, serverless-ready Discord bot framework.
160 lines (158 loc) • 6.62 kB
JavaScript
import ora from "ora";
import { Command } from "commander";
import { dirname, join, relative } from "node:path";
import { cwd, exit, stdout } from "node:process";
import { select, input, confirm } from "@inquirer/prompts";
import { parse } from "dotenv";
import { mkdirSync, writeFileSync } from "fs";
import build from "../server/build/build.js";
const program = new Command()
.name("dressed")
.description("A sleek, serverless-ready Discord bot framework.");
program
.command("build")
.description("Builds the bot and writes to .dressed")
.option("-i, --instance", "Include an instance create in the generated file")
.option("-r, --register", "Register slash commands")
.option("-e, --endpoint <endpoint>", "The endpoint to listen on, defaults to `/`")
.option("-p, --port <port>", "The port to listen on, defaults to `8000`")
.option("-R, --root <root>", "Source root for the bot, defaults to `src`")
.option("-E, --extensions <extensions>", "Comma separated list of file extensions to include when bundling handlers, defaults to `js, ts, mjs`")
.action(async ({ instance, register, endpoint, port, root, extensions }) => {
if (port && isNaN(Number(port))) {
ora("Port must be a valid number").fail();
return;
}
const { commands, components, events } = await build({
endpoint,
port: port ? Number(port) : undefined,
build: {
root,
extensions: extensions === null || extensions === void 0 ? void 0 : extensions.split(",").map((e) => e.trim()),
},
});
const buildLoader = ora({
stream: stdout,
text: "Assembling generated build",
}).start();
const outputContent = `
${generateImports(instance, register)}
import config from "./cache/config.mjs";${[
...commands,
...components,
...events,
]
.map((v) => `\nimport * as h${v.uid} from "./${relative(".dressed", v.path)}";`)
.join("")}
export const commands = [ ${commands.map((c) => JSON.stringify(c).replace("null", `h${c.uid}`))} ];
export const components = [ ${components.map((c) => JSON.stringify(c).replace("null", `h${c.uid}`))} ];
export const events = [ ${events.map((e) => JSON.stringify(e).replace("null", `h${e.uid}`))} ];
export { config };
${register ? "\ninstallCommands(commands);" : ""}
${instance ? `createServer(commands, components, events, config);` : ""}`.trim();
const typeContent = `
import type { CommandData, ComponentData, EventData, ServerConfig } from "dressed/server";
export declare const commands: CommandData[];
export declare const components: ComponentData[];
export declare const events: EventData[];
export declare const config: ServerConfig;`;
writeFileSync(".dressed/index.mjs", outputContent);
writeFileSync(".dressed/index.d.ts", typeContent);
buildLoader.succeed("Assembled generated build");
exit(0);
});
const generateImports = (addInstance, registerCommands) => addInstance || registerCommands
? `import { ${addInstance
? `createServer${registerCommands ? ", installCommands" : ""}`
: registerCommands
? "installCommands"
: ""} } from "dressed/server";`
: "";
program
.command("create")
.description("Clone a new bot from the examples repository")
.argument("[template]", "Template name (deno/economy)")
.argument("[name]", "Project name")
.action(async (name, template) => {
if (!name) {
name = await input({
message: "Project name:",
required: true,
});
}
if (!template ||
(!template.startsWith("node/") && !template.startsWith("deno/"))) {
const isDeno = await confirm({
message: "Would you like to use a Deno specific template?",
default: false,
});
const res = await fetch(`https://api.github.com/repos/inbestigator/dressed-examples/contents/${isDeno ? "deno" : "node"}`);
if (!res.ok) {
throw new Error("Failed to list templates.");
}
const files = (await res.json()).filter((f) => f.type === "dir");
template = await select({
message: "Select the template to use",
choices: files.map((f) => ({
name: f.name,
value: f.path,
})),
});
}
const res = await fetch(`https://raw.githubusercontent.com/inbestigator/dressed-examples/main/${template}/.env.example`);
if (!res.ok) {
throw new Error("Failed to fetch template.");
}
const parsed = parse(await res.text());
const envVars = {};
for (const [k, v] of Object.entries(parsed)) {
envVars[k] = await input({ message: k, default: v });
}
const mkdirLoader = ora(`Creating files for project: ${name}`).start();
async function createFiles(path, dest) {
mkdirSync(dest, { recursive: true });
const response = await fetch(path);
if (!response.ok) {
throw new Error(response.statusText);
}
const json = await response.json();
if (Array.isArray(json)) {
for (const file of json) {
if (file.type === "dir") {
await createFiles(file.url, join(dest, file.name));
}
else {
const fileRes = await fetch(file.download_url);
if (!fileRes.ok) {
throw new Error(fileRes.statusText);
}
const fileContents = await fileRes.text();
const destPath = join(dest, file.name);
if (file.name === ".env.example") {
const destPath = join(dest, ".env");
mkdirSync(dirname(destPath), { recursive: true });
writeFileSync(destPath, Object.entries(envVars)
.map(([k, v]) => `${k}="${v}"`)
.join("\n"));
}
mkdirSync(dirname(destPath), { recursive: true });
writeFileSync(destPath, fileContents);
}
}
}
}
try {
const path = `https://api.github.com/repos/inbestigator/dressed-examples/contents/${template}`;
await createFiles(path, join(cwd(), name));
}
catch (_a) {
mkdirLoader.fail();
return;
}
mkdirLoader.succeed();
console.log("\x1b[32m%s", "Project created successfully.");
exit(0);
});
program.parse();
//# sourceMappingURL=dressed.js.map