Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cyan-beers-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@djs-core/runtime": minor
"@djs-core/dev": minor
---

Add support of client.config (managed by djs-core)
3 changes: 3 additions & 0 deletions app/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"apiKeys": ["key1", "key2"]
}
1 change: 1 addition & 0 deletions app/djs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export default {
},
experimental: {
cron: true,
userConfig: true,
},
} satisfies Config;
2 changes: 1 addition & 1 deletion app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"@events/*": ["./src/events/*"]
}
},
"include": ["src/**/*"]
"include": ["src/**/*", ".djscore/**/*.d.ts"]
}
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
34 changes: 19 additions & 15 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 0 additions & 72 deletions docs/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"app"
],
"devDependencies": {
"@biomejs/biome": "2.3.10",
"@biomejs/biome": "^2.3.14",
"@changesets/cli": "^2.29.8",
"@types/node": "^25.0.3",
"knip": "^5.76.0",
Expand Down
18 changes: 16 additions & 2 deletions packages/dev/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fs from "fs/promises";
import path from "path";
import pc from "picocolors";
import { banner, PATH_ALIASES } from "../utils/common";
import { autoGenerateConfigTypes } from "../utils/config-type-generator";

declare const Bun: typeof import("bun");

Expand Down Expand Up @@ -76,6 +77,7 @@ function buildGeneratedEntry(opts: {
eventFiles: string[];
cronFiles: string[];
hasCronEnabled: boolean;
hasUserConfigEnabled: boolean;
}): string {
const {
genDir,
Expand Down Expand Up @@ -169,6 +171,7 @@ function buildGeneratedEntry(opts: {
import config from "../djs.config.ts";
import { DjsClient, type Route } from "@djs-core/runtime";
import { Events } from "discord.js";
${opts.hasUserConfigEnabled ? 'import type { UserConfig } from "./config.types.ts";\nimport userConfigData from "../config.json" with { type: "json" };' : ""}

${imports.join("\n")}

Expand Down Expand Up @@ -222,7 +225,11 @@ ${sortedCrons.map((c) => ` [${JSON.stringify(c.id)}, ${c.varName}],`).join("\
: ""
}

const client = new DjsClient({ djsConfig: config });
// Load user config if enabled. The type assertion is safe because:
// 1. The config.json is parsed and validated at build time
// 2. The UserConfig type is auto-generated from config.json structure
// 3. Any runtime mismatch will be caught during bot initialization
${opts.hasUserConfigEnabled ? " const client = new DjsClient<UserConfig>({ djsConfig: config, userConfig: userConfigData as UserConfig });" : " const client = new DjsClient({ djsConfig: config });"}

client.eventsHandler.set(events);

Expand Down Expand Up @@ -305,9 +312,15 @@ export function registerBuildCommand(cli: CAC) {

const configModule = await import(path.join(botRoot, "djs.config.ts"));
const config = configModule.default as {
experimental?: { cron?: boolean };
experimental?: { cron?: boolean; userConfig?: boolean };
};
const hasCronEnabled = config.experimental?.cron === true;
const hasUserConfigEnabled = config.experimental?.userConfig === true;

// Auto-generate config types if userConfig is enabled
if (hasUserConfigEnabled) {
await autoGenerateConfigTypes(botRoot, true);
}

const code = buildGeneratedEntry({
genDir,
Expand All @@ -322,6 +335,7 @@ export function registerBuildCommand(cli: CAC) {
eventFiles,
cronFiles,
hasCronEnabled,
hasUserConfigEnabled,
});

await fs.writeFile(entryPath, code, "utf8");
Expand Down
28 changes: 27 additions & 1 deletion packages/dev/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import {
UserSelectMenu,
} from "@djs-core/runtime";
import type { CAC } from "cac";
import chokidar from "chokidar";
import chokidar, { type FSWatcher } from "chokidar";
import path from "path";
import pc from "picocolors";
import { banner, PATH_ALIASES, runBot } from "../utils/common";
import { autoGenerateConfigTypes } from "../utils/config-type-generator";

type SelectMenu =
| StringSelectMenu
Expand Down Expand Up @@ -392,9 +393,34 @@ export function registerDevCommand(cli: CAC) {
.on("change", (p) => processFile("change", p))
.on("unlink", (p) => processFile("unlink", p));

let configWatcher: FSWatcher | null = null;
if (config.experimental?.userConfig) {
const configJsonPath = path.join(root, "config.json");
configWatcher = chokidar.watch(configJsonPath, {
ignoreInitial: true,
});

configWatcher.on("change", async () => {
console.log(
`${pc.cyan("ℹ")} config.json changed, regenerating types...`,
);
await autoGenerateConfigTypes(root);
});

configWatcher.on("add", async () => {
console.log(
`${pc.green("✓")} config.json created, generating types...`,
);
await autoGenerateConfigTypes(root);
});
}

process.on("SIGINT", async () => {
console.log(pc.dim("\nShutting down..."));
await watcher.close();
if (configWatcher) {
await configWatcher.close();
}
await client.destroy();
process.exit(0);
});
Expand Down
45 changes: 45 additions & 0 deletions packages/dev/commands/generate-config-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { CAC } from "cac";
import fs from "fs/promises";
import path from "path";
import pc from "picocolors";
import { banner } from "../utils/common";
import { generateTypesFromJson } from "../utils/config-type-generator";

export function registerGenerateConfigTypesCommand(cli: CAC) {
cli
.command(
"generate-config-types",
"Generate TypeScript types from config.json",
)
.option("-p, --path <path>", "Custom project path", { default: "." })
.action(async (options: { path: string }) => {
console.log(banner);
console.log(`${pc.cyan("ℹ")} Generating config types...`);

const projectRoot = path.resolve(process.cwd(), options.path);
const configJsonPath = path.join(projectRoot, "config.json");
const outputPath = path.join(projectRoot, ".djscore", "config.types.ts");

try {
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.access(configJsonPath);
} catch {
console.error(
pc.red(
`❌ config.json not found at ${configJsonPath}\n Create a config.json file first.`,
),
);
process.exit(1);
}

try {
await generateTypesFromJson(configJsonPath, outputPath);
console.log(
pc.green(`✓ Types generated successfully at ${outputPath}`),
);
} catch (error: unknown) {
console.error(pc.red("❌ Error generating types:"), error);
process.exit(1);
}
});
}
2 changes: 2 additions & 0 deletions packages/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { cac } from "cac";
import type { Config } from "../utils/types/config";
import { registerBuildCommand } from "./commands/build";
import { registerDevCommand } from "./commands/dev";
import { registerGenerateConfigTypesCommand } from "./commands/generate-config-types";
import { registerStartCommand } from "./commands/start";

export type { Config };
Expand All @@ -12,5 +13,6 @@ const cli = cac("djs-core").version("2.0.0").help();
registerStartCommand(cli);
registerDevCommand(cli);
registerBuildCommand(cli);
registerGenerateConfigTypesCommand(cli);

cli.parse();
Loading
Loading