From 78c614c69f77bd8098b93f6b31ffcb7ee3c0283e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:09:18 +0000 Subject: [PATCH 01/10] Initial plan From b8afc1719120a78865ced1eaf1e5566d65cd6d5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:12:15 +0000 Subject: [PATCH 02/10] Add user config feature support Co-authored-by: Cleboost <61158869+Cleboost@users.noreply.github.com> --- packages/dev/commands/build.ts | 8 +- .../dev/commands/generate-config-types.ts | 126 ++++++++++++++++++ packages/dev/index.ts | 2 + packages/dev/utils/common.ts | 18 ++- packages/runtime/DjsClient.ts | 6 +- packages/utils/types/config.d.ts | 1 + 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 packages/dev/commands/generate-config-types.ts diff --git a/packages/dev/commands/build.ts b/packages/dev/commands/build.ts index 6764f9c..2cd7a3d 100644 --- a/packages/dev/commands/build.ts +++ b/packages/dev/commands/build.ts @@ -76,6 +76,7 @@ function buildGeneratedEntry(opts: { eventFiles: string[]; cronFiles: string[]; hasCronEnabled: boolean; + hasUserConfigEnabled: boolean; }): string { const { genDir, @@ -169,6 +170,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")} @@ -222,7 +224,7 @@ ${sortedCrons.map((c) => ` [${JSON.stringify(c.id)}, ${c.varName}],`).join("\ : "" } - const client = new DjsClient({ djsConfig: config }); +${opts.hasUserConfigEnabled ? " const client = new DjsClient({ djsConfig: config, userConfig: userConfigData as UserConfig });" : " const client = new DjsClient({ djsConfig: config });"} client.eventsHandler.set(events); @@ -305,9 +307,10 @@ 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; const code = buildGeneratedEntry({ genDir, @@ -322,6 +325,7 @@ export function registerBuildCommand(cli: CAC) { eventFiles, cronFiles, hasCronEnabled, + hasUserConfigEnabled, }); await fs.writeFile(entryPath, code, "utf8"); diff --git a/packages/dev/commands/generate-config-types.ts b/packages/dev/commands/generate-config-types.ts new file mode 100644 index 0000000..31036f4 --- /dev/null +++ b/packages/dev/commands/generate-config-types.ts @@ -0,0 +1,126 @@ +import type { CAC } from "cac"; +import fs from "fs/promises"; +import path from "path"; +import pc from "picocolors"; +import { banner } from "../utils/common"; + +async function generateTypesFromJson( + configJsonPath: string, + outputPath: string, +): Promise { + const jsonContent = await fs.readFile(configJsonPath, "utf-8"); + const config = JSON.parse(jsonContent); + + const typeDefinition = generateTypeDefinition(config, "UserConfig"); + + const fileContent = `// Auto-generated from config.json. Do not edit manually. + +${typeDefinition} + +export type { UserConfig }; +`; + + await fs.writeFile(outputPath, fileContent, "utf-8"); +} + +function generateTypeDefinition( + obj: unknown, + typeName: string, + indent = 0, +): string { + if (obj === null) return "null"; + if (obj === undefined) return "undefined"; + + const indentStr = " ".repeat(indent); + const nextIndentStr = " ".repeat(indent + 1); + + if (Array.isArray(obj)) { + if (obj.length === 0) return "unknown[]"; + const firstElement = obj[0]; + const elementType = inferType(firstElement); + return `${elementType}[]`; + } + + if (typeof obj === "object") { + const entries = Object.entries(obj); + if (entries.length === 0) return "Record"; + + const properties = entries + .map(([key, value]) => { + const valueType = inferType(value); + return `${nextIndentStr}${key}: ${valueType};`; + }) + .join("\n"); + + return `${indent === 0 ? "" : "\n"}${indentStr}interface ${typeName} {\n${properties}\n${indentStr}}`; + } + + return inferType(obj); +} + +function inferType(value: unknown): string { + if (value === null) return "null"; + if (value === undefined) return "undefined"; + if (typeof value === "string") return "string"; + if (typeof value === "number") return "number"; + if (typeof value === "boolean") return "boolean"; + + if (Array.isArray(value)) { + if (value.length === 0) return "unknown[]"; + const firstElement = value[0]; + const elementType = inferType(firstElement); + const allSameType = value.every((v) => inferType(v) === elementType); + return allSameType ? `${elementType}[]` : "unknown[]"; + } + + if (typeof value === "object") { + const entries = Object.entries(value); + if (entries.length === 0) return "Record"; + + const properties = entries + .map(([key, val]) => { + const valueType = inferType(val); + return `${key}: ${valueType}`; + }) + .join("; "); + + return `{ ${properties} }`; + } + + return "unknown"; +} + +export function registerGenerateConfigTypesCommand(cli: CAC) { + cli + .command("generate-config-types", "Generate TypeScript types from config.json") + .option("-p, --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, "config.types.ts"); + + try { + 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); + } + }); +} diff --git a/packages/dev/index.ts b/packages/dev/index.ts index bf87ddf..ec66766 100644 --- a/packages/dev/index.ts +++ b/packages/dev/index.ts @@ -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 }; @@ -12,5 +13,6 @@ const cli = cac("djs-core").version("2.0.0").help(); registerStartCommand(cli); registerDevCommand(cli); registerBuildCommand(cli); +registerGenerateConfigTypesCommand(cli); cli.parse(); diff --git a/packages/dev/utils/common.ts b/packages/dev/utils/common.ts index 8b59b4c..912ad08 100644 --- a/packages/dev/utils/common.ts +++ b/packages/dev/utils/common.ts @@ -137,7 +137,23 @@ export async function runBot(projectPath: string) { console.log(`${pc.green("✓")} Loaded ${pc.bold(tasks.size)} cron tasks`); } - const client = new DjsClient({ djsConfig: config }); + let userConfig: unknown = undefined; + if (config.experimental?.userConfig) { + try { + const configJsonPath = path.join(root, "config.json"); + const configJsonContent = await fs.readFile(configJsonPath, "utf-8"); + userConfig = JSON.parse(configJsonContent); + console.log(`${pc.green("✓")} User config loaded`); + } catch (error) { + console.warn( + pc.yellow( + "⚠️ userConfig is enabled but config.json not found or invalid", + ), + ); + } + } + + const client = new DjsClient({ djsConfig: config, userConfig }); client.eventsHandler.set(events); diff --git a/packages/runtime/DjsClient.ts b/packages/runtime/DjsClient.ts index d5b7e40..7af229a 100644 --- a/packages/runtime/DjsClient.ts +++ b/packages/runtime/DjsClient.ts @@ -25,7 +25,7 @@ import ModalHandler from "./handler/ModalHandler"; import SelectMenuHandler from "./handler/SelectMenuHandler"; import { cleanupExpiredTokens } from "./store/DataStore"; -export default class DjsClient extends Client { +export default class DjsClient extends Client { public eventsHandler: EventHandler = new EventHandler(this); public commandsHandler: CommandHandler = new CommandHandler(this); public buttonsHandler: ButtonHandler = new ButtonHandler(this); @@ -36,8 +36,9 @@ export default class DjsClient extends Client { new ApplicationCommandHandler(this); public cronHandler: CronHandler = new CronHandler(this); private readonly djsConfig: Config; + public readonly conf: UserConfig; - constructor({ djsConfig }: { djsConfig: Config }) { + constructor({ djsConfig, userConfig }: { djsConfig: Config; userConfig?: UserConfig }) { super({ intents: [ IntentsBitField.Flags.Guilds, @@ -47,6 +48,7 @@ export default class DjsClient extends Client { ], }); this.djsConfig = djsConfig; + this.conf = userConfig as UserConfig; if (djsConfig.servers && djsConfig.servers.length > 0) { this.commandsHandler.setGuilds(djsConfig.servers); diff --git a/packages/utils/types/config.d.ts b/packages/utils/types/config.d.ts index 50c3511..39ab646 100644 --- a/packages/utils/types/config.d.ts +++ b/packages/utils/types/config.d.ts @@ -8,5 +8,6 @@ export interface Config { }; experimental?: { cron?: boolean; + userConfig?: boolean; }; } From 36e92a9781e0185c0e1c6d3761fc3cfe3c4000f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:15:11 +0000 Subject: [PATCH 03/10] Add user config example and documentation Co-authored-by: Cleboost <61158869+Cleboost@users.noreply.github.com> --- app/config.json | 12 +++ app/config.types.ts | 9 ++ app/djs.config.ts | 1 + app/src/events/ready-with-config.ts.example | 20 ++++ docs/USER_CONFIG.md | 100 ++++++++++++++++++++ 5 files changed, 142 insertions(+) create mode 100644 app/config.json create mode 100644 app/config.types.ts create mode 100644 app/src/events/ready-with-config.ts.example create mode 100644 docs/USER_CONFIG.md diff --git a/app/config.json b/app/config.json new file mode 100644 index 0000000..8104e90 --- /dev/null +++ b/app/config.json @@ -0,0 +1,12 @@ +{ + "database": { + "host": "localhost", + "port": 5432, + "name": "mydb" + }, + "features": { + "premium": true, + "maxUsers": 100 + }, + "apiKeys": ["key1", "key2"] +} diff --git a/app/config.types.ts b/app/config.types.ts new file mode 100644 index 0000000..174dc01 --- /dev/null +++ b/app/config.types.ts @@ -0,0 +1,9 @@ +// Auto-generated from config.json. Do not edit manually. + +interface UserConfig { + database: { host: string; port: number; name: string }; + features: { premium: boolean; maxUsers: number }; + apiKeys: string[]; +} + +export type { UserConfig }; diff --git a/app/djs.config.ts b/app/djs.config.ts index ad0d4f6..46bfc69 100644 --- a/app/djs.config.ts +++ b/app/djs.config.ts @@ -13,5 +13,6 @@ export default { }, experimental: { cron: true, + userConfig: true, }, } satisfies Config; diff --git a/app/src/events/ready-with-config.ts.example b/app/src/events/ready-with-config.ts.example new file mode 100644 index 0000000..d2bdf7f --- /dev/null +++ b/app/src/events/ready-with-config.ts.example @@ -0,0 +1,20 @@ +import { EventListner } from "@djs-core/runtime"; +import type { UserConfig } from "../../config.types"; +import { ActivityType, Events } from "discord.js"; + +export default new EventListner() + .event(Events.ClientReady) + .run((client) => { + client.user?.setActivity({ name: "🥖 bread", type: ActivityType.Custom }); + + // Example: Access user config + if (client.conf) { + console.log("User config loaded:"); + console.log( + ` Database: ${client.conf.database.host}:${client.conf.database.port}`, + ); + console.log(` Features - Premium: ${client.conf.features.premium}`); + console.log(` Features - Max Users: ${client.conf.features.maxUsers}`); + console.log(` API Keys: ${client.conf.apiKeys.length} configured`); + } + }); diff --git a/docs/USER_CONFIG.md b/docs/USER_CONFIG.md new file mode 100644 index 0000000..9d470d5 --- /dev/null +++ b/docs/USER_CONFIG.md @@ -0,0 +1,100 @@ +# User Config Feature + +This feature allows you to define custom configuration in a `config.json` file and access it with full TypeScript type safety through `client.conf`. + +## Setup + +### 1. Enable User Config + +In your `djs.config.ts`, enable the experimental `userConfig` feature: + +```typescript +import type { Config } from "@djs-core/dev"; + +export default { + token: process.env.TOKEN, + servers: ["your-server-id"], + experimental: { + userConfig: true, // Enable user config + }, +} satisfies Config; +``` + +### 2. Create config.json + +Create a `config.json` file in your project root with your custom configuration: + +```json +{ + "database": { + "host": "localhost", + "port": 5432, + "name": "mydb" + }, + "features": { + "premium": true, + "maxUsers": 100 + }, + "apiKeys": ["key1", "key2"] +} +``` + +### 3. Generate TypeScript Types + +Run the CLI command to generate TypeScript types from your `config.json`: + +```bash +bun djs-core generate-config-types +# or with a custom path +bun djs-core generate-config-types -p ./path/to/your/bot +``` + +This will create a `config.types.ts` file with the inferred TypeScript types: + +```typescript +// Auto-generated from config.json. Do not edit manually. + +interface UserConfig { + database: { host: string; port: number; name: string }; + features: { premium: boolean; maxUsers: number }; + apiKeys: string[]; +} + +export type { UserConfig }; +``` + +### 4. Use the Config in Your Code + +Access your config through `client.conf` with full type safety: + +```typescript +import { EventListner } from "@djs-core/runtime"; +import type { UserConfig } from "../config.types"; +import { Events } from "discord.js"; + +export default new EventListner() + .event(Events.ClientReady) + .run((client) => { + // Access your config with full TypeScript autocomplete and type checking + console.log(`Database: ${client.conf.database.host}:${client.conf.database.port}`); + console.log(`Premium enabled: ${client.conf.features.premium}`); + console.log(`Max users: ${client.conf.features.maxUsers}`); + console.log(`API Keys: ${client.conf.apiKeys.length}`); + }); +``` + +## Notes + +- The `config.json` file is loaded at runtime +- The `config.types.ts` file is auto-generated and should not be edited manually +- Re-run `generate-config-types` whenever you update your `config.json` +- The `client.conf` property is typed as `UserConfig` when using the generic type parameter +- If `userConfig` is not enabled, `client.conf` will be `undefined` + +## Example Use Cases + +- Database connection settings +- Feature flags +- API keys and credentials (ensure proper security measures) +- Custom bot configuration per environment +- Application-specific settings From 9fe80303404c842a39e45838aa479fc15966c98d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:16:52 +0000 Subject: [PATCH 04/10] Fix code review issues - make conf optional and add documentation Co-authored-by: Cleboost <61158869+Cleboost@users.noreply.github.com> --- docs/USER_CONFIG.md | 1 + packages/dev/commands/build.ts | 4 ++++ packages/runtime/DjsClient.ts | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/USER_CONFIG.md b/docs/USER_CONFIG.md index 9d470d5..3c22c6a 100644 --- a/docs/USER_CONFIG.md +++ b/docs/USER_CONFIG.md @@ -90,6 +90,7 @@ export default new EventListner() - Re-run `generate-config-types` whenever you update your `config.json` - The `client.conf` property is typed as `UserConfig` when using the generic type parameter - If `userConfig` is not enabled, `client.conf` will be `undefined` +- Empty arrays in config.json are typed as `unknown[]` - add at least one element for better type inference ## Example Use Cases diff --git a/packages/dev/commands/build.ts b/packages/dev/commands/build.ts index 2cd7a3d..2495386 100644 --- a/packages/dev/commands/build.ts +++ b/packages/dev/commands/build.ts @@ -224,6 +224,10 @@ ${sortedCrons.map((c) => ` [${JSON.stringify(c.id)}, ${c.varName}],`).join("\ : "" } + // 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({ djsConfig: config, userConfig: userConfigData as UserConfig });" : " const client = new DjsClient({ djsConfig: config });"} client.eventsHandler.set(events); diff --git a/packages/runtime/DjsClient.ts b/packages/runtime/DjsClient.ts index 7af229a..b779404 100644 --- a/packages/runtime/DjsClient.ts +++ b/packages/runtime/DjsClient.ts @@ -36,7 +36,7 @@ export default class DjsClient extends Client { new ApplicationCommandHandler(this); public cronHandler: CronHandler = new CronHandler(this); private readonly djsConfig: Config; - public readonly conf: UserConfig; + public readonly conf?: UserConfig; constructor({ djsConfig, userConfig }: { djsConfig: Config; userConfig?: UserConfig }) { super({ From 73f72a1012d4200cfb5c106b9e1e754cd7c1e6f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:28:25 +0000 Subject: [PATCH 05/10] Auto-generate config types on dev/build/start commands Co-authored-by: Cleboost <61158869+Cleboost@users.noreply.github.com> --- docs/USER_CONFIG.md | 27 +++- packages/dev/commands/build.ts | 6 + packages/dev/commands/dev.ts | 27 ++++ .../dev/commands/generate-config-types.ts | 87 +----------- packages/dev/utils/common.ts | 6 + packages/dev/utils/config-type-generator.ts | 128 ++++++++++++++++++ 6 files changed, 188 insertions(+), 93 deletions(-) create mode 100644 packages/dev/utils/config-type-generator.ts diff --git a/docs/USER_CONFIG.md b/docs/USER_CONFIG.md index 3c22c6a..703ea9a 100644 --- a/docs/USER_CONFIG.md +++ b/docs/USER_CONFIG.md @@ -39,17 +39,17 @@ Create a `config.json` file in your project root with your custom configuration: } ``` -### 3. Generate TypeScript Types +### 3. Types Auto-Generated -Run the CLI command to generate TypeScript types from your `config.json`: +When you run `dev`, `build`, or `start`, the TypeScript types are **automatically generated** from your `config.json`: ```bash -bun djs-core generate-config-types -# or with a custom path -bun djs-core generate-config-types -p ./path/to/your/bot +bun djs-core dev # Types auto-generated +bun djs-core build # Types auto-generated +bun djs-core start # Types auto-generated ``` -This will create a `config.types.ts` file with the inferred TypeScript types: +A `config.types.ts` file will be created automatically: ```typescript // Auto-generated from config.json. Do not edit manually. @@ -63,6 +63,8 @@ interface UserConfig { export type { UserConfig }; ``` +**In dev mode**, types are regenerated automatically when you modify `config.json`. + ### 4. Use the Config in Your Code Access your config through `client.conf` with full type safety: @@ -83,11 +85,22 @@ export default new EventListner() }); ``` +## Manual Type Generation (Optional) + +If you need to generate types manually (e.g., for IDE refresh), you can use: + +```bash +bun djs-core generate-config-types +``` + +This is optional since types are auto-generated when running dev/build/start. + ## Notes - The `config.json` file is loaded at runtime - The `config.types.ts` file is auto-generated and should not be edited manually -- Re-run `generate-config-types` whenever you update your `config.json` +- Types are **automatically regenerated** when you run `dev`, `build`, or `start` +- In **dev mode**, types regenerate when you modify `config.json` - The `client.conf` property is typed as `UserConfig` when using the generic type parameter - If `userConfig` is not enabled, `client.conf` will be `undefined` - Empty arrays in config.json are typed as `unknown[]` - add at least one element for better type inference diff --git a/packages/dev/commands/build.ts b/packages/dev/commands/build.ts index 2495386..bbc9f53 100644 --- a/packages/dev/commands/build.ts +++ b/packages/dev/commands/build.ts @@ -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"); @@ -316,6 +317,11 @@ export function registerBuildCommand(cli: CAC) { 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, commandsDir, diff --git a/packages/dev/commands/dev.ts b/packages/dev/commands/dev.ts index 557b23c..21da5be 100644 --- a/packages/dev/commands/dev.ts +++ b/packages/dev/commands/dev.ts @@ -17,6 +17,7 @@ import chokidar 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 @@ -392,6 +393,32 @@ export function registerDevCommand(cli: CAC) { .on("change", (p) => processFile("change", p)) .on("unlink", (p) => processFile("unlink", p)); + // Watch config.json for changes if userConfig is enabled + if (config.experimental?.userConfig) { + const configJsonPath = path.join(root, "config.json"); + const 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 () => { + await configWatcher.close(); + }); + } + process.on("SIGINT", async () => { console.log(pc.dim("\nShutting down...")); await watcher.close(); diff --git a/packages/dev/commands/generate-config-types.ts b/packages/dev/commands/generate-config-types.ts index 31036f4..8457ddf 100644 --- a/packages/dev/commands/generate-config-types.ts +++ b/packages/dev/commands/generate-config-types.ts @@ -3,92 +3,7 @@ import fs from "fs/promises"; import path from "path"; import pc from "picocolors"; import { banner } from "../utils/common"; - -async function generateTypesFromJson( - configJsonPath: string, - outputPath: string, -): Promise { - const jsonContent = await fs.readFile(configJsonPath, "utf-8"); - const config = JSON.parse(jsonContent); - - const typeDefinition = generateTypeDefinition(config, "UserConfig"); - - const fileContent = `// Auto-generated from config.json. Do not edit manually. - -${typeDefinition} - -export type { UserConfig }; -`; - - await fs.writeFile(outputPath, fileContent, "utf-8"); -} - -function generateTypeDefinition( - obj: unknown, - typeName: string, - indent = 0, -): string { - if (obj === null) return "null"; - if (obj === undefined) return "undefined"; - - const indentStr = " ".repeat(indent); - const nextIndentStr = " ".repeat(indent + 1); - - if (Array.isArray(obj)) { - if (obj.length === 0) return "unknown[]"; - const firstElement = obj[0]; - const elementType = inferType(firstElement); - return `${elementType}[]`; - } - - if (typeof obj === "object") { - const entries = Object.entries(obj); - if (entries.length === 0) return "Record"; - - const properties = entries - .map(([key, value]) => { - const valueType = inferType(value); - return `${nextIndentStr}${key}: ${valueType};`; - }) - .join("\n"); - - return `${indent === 0 ? "" : "\n"}${indentStr}interface ${typeName} {\n${properties}\n${indentStr}}`; - } - - return inferType(obj); -} - -function inferType(value: unknown): string { - if (value === null) return "null"; - if (value === undefined) return "undefined"; - if (typeof value === "string") return "string"; - if (typeof value === "number") return "number"; - if (typeof value === "boolean") return "boolean"; - - if (Array.isArray(value)) { - if (value.length === 0) return "unknown[]"; - const firstElement = value[0]; - const elementType = inferType(firstElement); - const allSameType = value.every((v) => inferType(v) === elementType); - return allSameType ? `${elementType}[]` : "unknown[]"; - } - - if (typeof value === "object") { - const entries = Object.entries(value); - if (entries.length === 0) return "Record"; - - const properties = entries - .map(([key, val]) => { - const valueType = inferType(val); - return `${key}: ${valueType}`; - }) - .join("; "); - - return `{ ${properties} }`; - } - - return "unknown"; -} +import { generateTypesFromJson } from "../utils/config-type-generator"; export function registerGenerateConfigTypesCommand(cli: CAC) { cli diff --git a/packages/dev/utils/common.ts b/packages/dev/utils/common.ts index 912ad08..66d7eef 100644 --- a/packages/dev/utils/common.ts +++ b/packages/dev/utils/common.ts @@ -26,6 +26,7 @@ import fs from "fs/promises"; import path, { resolve } from "path"; import pc from "picocolors"; import type { Config } from "../../utils/types/config"; +import { autoGenerateConfigTypes } from "./config-type-generator"; export const banner = ` ${pc.bold(pc.blue("djs-core"))} ${pc.dim(`v1.0.0`)} @@ -54,6 +55,11 @@ export async function runBot(projectPath: string) { console.log(`${pc.green("✓")} Config loaded`); + // Auto-generate config types if userConfig is enabled + if (config.experimental?.userConfig) { + await autoGenerateConfigTypes(root); + } + const commands: Route[] = []; const buttons: Button[] = []; const contextMenus: ContextMenu[] = []; diff --git a/packages/dev/utils/config-type-generator.ts b/packages/dev/utils/config-type-generator.ts new file mode 100644 index 0000000..621e945 --- /dev/null +++ b/packages/dev/utils/config-type-generator.ts @@ -0,0 +1,128 @@ +import fs from "fs/promises"; +import path from "path"; +import pc from "picocolors"; + +function inferType(value: unknown): string { + if (value === null) return "null"; + if (value === undefined) return "undefined"; + if (typeof value === "string") return "string"; + if (typeof value === "number") return "number"; + if (typeof value === "boolean") return "boolean"; + + if (Array.isArray(value)) { + if (value.length === 0) return "unknown[]"; + const firstElement = value[0]; + const elementType = inferType(firstElement); + const allSameType = value.every((v) => inferType(v) === elementType); + return allSameType ? `${elementType}[]` : "unknown[]"; + } + + if (typeof value === "object") { + const entries = Object.entries(value); + if (entries.length === 0) return "Record"; + + const properties = entries + .map(([key, val]) => { + const valueType = inferType(val); + return `${key}: ${valueType}`; + }) + .join("; "); + + return `{ ${properties} }`; + } + + return "unknown"; +} + +function generateTypeDefinition( + obj: unknown, + typeName: string, + indent = 0, +): string { + if (obj === null) return "null"; + if (obj === undefined) return "undefined"; + + const indentStr = " ".repeat(indent); + const nextIndentStr = " ".repeat(indent + 1); + + if (Array.isArray(obj)) { + if (obj.length === 0) return "unknown[]"; + const firstElement = obj[0]; + const elementType = inferType(firstElement); + return `${elementType}[]`; + } + + if (typeof obj === "object") { + const entries = Object.entries(obj); + if (entries.length === 0) return "Record"; + + const properties = entries + .map(([key, value]) => { + const valueType = inferType(value); + return `${nextIndentStr}${key}: ${valueType};`; + }) + .join("\n"); + + return `${indent === 0 ? "" : "\n"}${indentStr}interface ${typeName} {\n${properties}\n${indentStr}}`; + } + + return inferType(obj); +} + +export async function generateTypesFromJson( + configJsonPath: string, + outputPath: string, +): Promise { + const jsonContent = await fs.readFile(configJsonPath, "utf-8"); + const config = JSON.parse(jsonContent); + + const typeDefinition = generateTypeDefinition(config, "UserConfig"); + + const fileContent = `// Auto-generated from config.json. Do not edit manually. + +${typeDefinition} + +export type { UserConfig }; +`; + + await fs.writeFile(outputPath, fileContent, "utf-8"); +} + +/** + * Auto-generate config types if userConfig is enabled and config.json exists + * This is called automatically by dev/build/start commands + */ +export async function autoGenerateConfigTypes( + projectRoot: string, + silent = false, +): Promise { + const configJsonPath = path.join(projectRoot, "config.json"); + const outputPath = path.join(projectRoot, "config.types.ts"); + + try { + await fs.access(configJsonPath); + } catch { + // config.json doesn't exist, skip generation + if (!silent) { + console.log( + pc.yellow( + "⚠️ userConfig enabled but config.json not found. Skipping type generation.", + ), + ); + } + return false; + } + + try { + await generateTypesFromJson(configJsonPath, outputPath); + if (!silent) { + console.log(pc.green("✓ Config types auto-generated")); + } + return true; + } catch (error: unknown) { + if (!silent) { + console.warn(pc.yellow("⚠️ Error generating config types:"), error); + } + return false; + } +} From 240367f837b5b03ebe1d2ccc4ba0eee4ddf68f7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:30:02 +0000 Subject: [PATCH 06/10] Fix SIGINT handler and improve error messages Co-authored-by: Cleboost <61158869+Cleboost@users.noreply.github.com> --- packages/dev/commands/dev.ts | 10 +++++----- packages/dev/utils/config-type-generator.ts | 12 +++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/dev/commands/dev.ts b/packages/dev/commands/dev.ts index 21da5be..cb6395d 100644 --- a/packages/dev/commands/dev.ts +++ b/packages/dev/commands/dev.ts @@ -394,9 +394,10 @@ export function registerDevCommand(cli: CAC) { .on("unlink", (p) => processFile("unlink", p)); // Watch config.json for changes if userConfig is enabled + let configWatcher: chokidar.FSWatcher | null = null; if (config.experimental?.userConfig) { const configJsonPath = path.join(root, "config.json"); - const configWatcher = chokidar.watch(configJsonPath, { + configWatcher = chokidar.watch(configJsonPath, { ignoreInitial: true, }); @@ -413,15 +414,14 @@ export function registerDevCommand(cli: CAC) { ); await autoGenerateConfigTypes(root); }); - - process.on("SIGINT", async () => { - await configWatcher.close(); - }); } process.on("SIGINT", async () => { console.log(pc.dim("\nShutting down...")); await watcher.close(); + if (configWatcher) { + await configWatcher.close(); + } await client.destroy(); process.exit(0); }); diff --git a/packages/dev/utils/config-type-generator.ts b/packages/dev/utils/config-type-generator.ts index 621e945..90bb654 100644 --- a/packages/dev/utils/config-type-generator.ts +++ b/packages/dev/utils/config-type-generator.ts @@ -121,7 +121,17 @@ export async function autoGenerateConfigTypes( return true; } catch (error: unknown) { if (!silent) { - console.warn(pc.yellow("⚠️ Error generating config types:"), error); + console.warn( + pc.yellow( + `⚠️ Error generating config types from ${configJsonPath}`, + ), + ); + console.warn( + pc.dim(" Possible causes: invalid JSON syntax, file permissions"), + ); + if (error instanceof Error) { + console.warn(pc.dim(` ${error.message}`)); + } } return false; } From 49de492dff35018c04a9e758d05004c294e0f951 Mon Sep 17 00:00:00 2001 From: Cleboost Date: Sun, 8 Feb 2026 17:47:01 +0100 Subject: [PATCH 07/10] Update user config feature: remove deprecated config structure, enhance type generation, and adjust TypeScript configuration. Clean up related documentation and example files. --- app/config.json | 9 -- app/config.types.ts | 9 -- app/src/events/ready-with-config.ts.example | 20 --- app/tsconfig.json | 2 +- bun.lock | 14 ++- docs/README.md | 72 ----------- docs/USER_CONFIG.md | 114 ------------------ packages/dev/commands/build.ts | 2 +- packages/dev/commands/dev.ts | 5 +- .../dev/commands/generate-config-types.ts | 3 +- packages/dev/utils/config-type-generator.ts | 64 +++++++++- packages/runtime/DjsClient.ts | 4 +- 12 files changed, 80 insertions(+), 238 deletions(-) delete mode 100644 app/config.types.ts delete mode 100644 app/src/events/ready-with-config.ts.example delete mode 100644 docs/README.md delete mode 100644 docs/USER_CONFIG.md diff --git a/app/config.json b/app/config.json index 8104e90..57e30fd 100644 --- a/app/config.json +++ b/app/config.json @@ -1,12 +1,3 @@ { - "database": { - "host": "localhost", - "port": 5432, - "name": "mydb" - }, - "features": { - "premium": true, - "maxUsers": 100 - }, "apiKeys": ["key1", "key2"] } diff --git a/app/config.types.ts b/app/config.types.ts deleted file mode 100644 index 174dc01..0000000 --- a/app/config.types.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Auto-generated from config.json. Do not edit manually. - -interface UserConfig { - database: { host: string; port: number; name: string }; - features: { premium: boolean; maxUsers: number }; - apiKeys: string[]; -} - -export type { UserConfig }; diff --git a/app/src/events/ready-with-config.ts.example b/app/src/events/ready-with-config.ts.example deleted file mode 100644 index d2bdf7f..0000000 --- a/app/src/events/ready-with-config.ts.example +++ /dev/null @@ -1,20 +0,0 @@ -import { EventListner } from "@djs-core/runtime"; -import type { UserConfig } from "../../config.types"; -import { ActivityType, Events } from "discord.js"; - -export default new EventListner() - .event(Events.ClientReady) - .run((client) => { - client.user?.setActivity({ name: "🥖 bread", type: ActivityType.Custom }); - - // Example: Access user config - if (client.conf) { - console.log("User config loaded:"); - console.log( - ` Database: ${client.conf.database.host}:${client.conf.database.port}`, - ); - console.log(` Features - Premium: ${client.conf.features.premium}`); - console.log(` Features - Max Users: ${client.conf.features.maxUsers}`); - console.log(` API Keys: ${client.conf.apiKeys.length} configured`); - } - }); diff --git a/app/tsconfig.json b/app/tsconfig.json index 7a8c1b3..02cbc49 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -8,5 +8,5 @@ "@events/*": ["./src/events/*"] } }, - "include": ["src/**/*"] + "include": ["src/**/*", ".djscore/**/*.d.ts"] } diff --git a/bun.lock b/bun.lock index 7b449e8..9c1ffe4 100644 --- a/bun.lock +++ b/bun.lock @@ -27,12 +27,12 @@ }, "packages/dev": { "name": "@djs-core/dev", - "version": "2.0.0", + "version": "3.0.0", "bin": { "djs-core": "dist/index.js", }, "dependencies": { - "@clack/prompts": "^0.11.0", + "@clack/prompts": "^1.0.0", "cac": "^6.7.14", "chokidar": "^5.0.0", "picocolors": "^1.1.1", @@ -49,7 +49,7 @@ }, "packages/runtime": { "name": "@djs-core/runtime", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "cron": "^4.4.0", }, @@ -118,9 +118,9 @@ "@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="], - "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], + "@clack/core": ["@clack/core@1.0.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-Orf9Ltr5NeiEuVJS8Rk2XTw3IxNC2Bic3ash7GgYeA8LJ/zmSNpSQ/m5UAhe03lA6KFgklzZ5KTHs4OAMA/SAQ=="], - "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], + "@clack/prompts": ["@clack/prompts@1.0.0", "", { "dependencies": { "@clack/core": "1.0.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rWPXg9UaCFqErJVQ+MecOaWsozjaxol4yjnmYcGNipAWzdaWa2x+VJmKfGq7L0APwBohQOYdHC+9RO4qRXej+A=="], "@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="], @@ -454,6 +454,8 @@ "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + "@djs-core/runtime/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], "@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], @@ -466,6 +468,8 @@ "read-yaml-file/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "@djs-core/runtime/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], } } diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 2c06ff9..0000000 --- a/docs/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Docus Default Starter - -> A beautiful, minimal starter for creating documentation with Docus - -This is the default Docus starter template that provides everything you need to build beautiful documentation sites with Markdown and Vue components. - -> [!TIP] -> If you're looking for i18n support, check out the [i18n starter](https://github.com/nuxt-themes/docus/tree/main/.starters/i18n). - -## ✨ Features - -- 🎨 **Beautiful Design** - Clean, modern documentation theme -- 📱 **Responsive** - Mobile-first responsive design -- 🌙 **Dark Mode** - Built-in dark/light mode support -- 🔍 **Search** - Full-text search functionality -- 📝 **Markdown Enhanced** - Extended markdown with custom components -- 🎨 **Customizable** - Easy theming and brand customization -- ⚡ **Fast** - Optimized for performance with Nuxt 4 -- 🔧 **TypeScript** - Full TypeScript support - -## 🚀 Quick Start - -```bash -# Install dependencies -npm install - -# Start development server -npm run dev -``` - -Your documentation site will be running at `http://localhost:3000` - -## 📁 Project Structure - -``` -my-docs/ -├── content/ # Your markdown content -│ ├── index.md # Homepage -│ ├── 1.getting-started/ # Getting started section -│ └── 2.essentials/ # Essential documentation -├── public/ # Static assets -└── package.json # Dependencies and scripts -``` - -## ⚡ Built with - -This starter comes pre-configured with: - -- [Nuxt 4](https://nuxt.com) - The web framework -- [Nuxt Content](https://content.nuxt.com/) - File-based CMS -- [Nuxt UI](https://ui.nuxt.com) - UI components -- [Nuxt Image](https://image.nuxt.com/) - Optimized images -- [Tailwind CSS 4](https://tailwindcss.com/) - Utility-first CSS -- [Docus Layer](https://www.npmjs.com/package/docus) - Documentation theme - -## 📖 Documentation - -For detailed documentation on customizing your Docus project, visit the [Docus Documentation](https://docus.dev) - -## 🚀 Deployment - -Build for production: - -```bash -npm run build -``` - -The built files will be in the `.output` directory, ready for deployment to any hosting provider that supports Node.js. - -## 📄 License - -[MIT License](https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/docs/USER_CONFIG.md b/docs/USER_CONFIG.md deleted file mode 100644 index 703ea9a..0000000 --- a/docs/USER_CONFIG.md +++ /dev/null @@ -1,114 +0,0 @@ -# User Config Feature - -This feature allows you to define custom configuration in a `config.json` file and access it with full TypeScript type safety through `client.conf`. - -## Setup - -### 1. Enable User Config - -In your `djs.config.ts`, enable the experimental `userConfig` feature: - -```typescript -import type { Config } from "@djs-core/dev"; - -export default { - token: process.env.TOKEN, - servers: ["your-server-id"], - experimental: { - userConfig: true, // Enable user config - }, -} satisfies Config; -``` - -### 2. Create config.json - -Create a `config.json` file in your project root with your custom configuration: - -```json -{ - "database": { - "host": "localhost", - "port": 5432, - "name": "mydb" - }, - "features": { - "premium": true, - "maxUsers": 100 - }, - "apiKeys": ["key1", "key2"] -} -``` - -### 3. Types Auto-Generated - -When you run `dev`, `build`, or `start`, the TypeScript types are **automatically generated** from your `config.json`: - -```bash -bun djs-core dev # Types auto-generated -bun djs-core build # Types auto-generated -bun djs-core start # Types auto-generated -``` - -A `config.types.ts` file will be created automatically: - -```typescript -// Auto-generated from config.json. Do not edit manually. - -interface UserConfig { - database: { host: string; port: number; name: string }; - features: { premium: boolean; maxUsers: number }; - apiKeys: string[]; -} - -export type { UserConfig }; -``` - -**In dev mode**, types are regenerated automatically when you modify `config.json`. - -### 4. Use the Config in Your Code - -Access your config through `client.conf` with full type safety: - -```typescript -import { EventListner } from "@djs-core/runtime"; -import type { UserConfig } from "../config.types"; -import { Events } from "discord.js"; - -export default new EventListner() - .event(Events.ClientReady) - .run((client) => { - // Access your config with full TypeScript autocomplete and type checking - console.log(`Database: ${client.conf.database.host}:${client.conf.database.port}`); - console.log(`Premium enabled: ${client.conf.features.premium}`); - console.log(`Max users: ${client.conf.features.maxUsers}`); - console.log(`API Keys: ${client.conf.apiKeys.length}`); - }); -``` - -## Manual Type Generation (Optional) - -If you need to generate types manually (e.g., for IDE refresh), you can use: - -```bash -bun djs-core generate-config-types -``` - -This is optional since types are auto-generated when running dev/build/start. - -## Notes - -- The `config.json` file is loaded at runtime -- The `config.types.ts` file is auto-generated and should not be edited manually -- Types are **automatically regenerated** when you run `dev`, `build`, or `start` -- In **dev mode**, types regenerate when you modify `config.json` -- The `client.conf` property is typed as `UserConfig` when using the generic type parameter -- If `userConfig` is not enabled, `client.conf` will be `undefined` -- Empty arrays in config.json are typed as `unknown[]` - add at least one element for better type inference - -## Example Use Cases - -- Database connection settings -- Feature flags -- API keys and credentials (ensure proper security measures) -- Custom bot configuration per environment -- Application-specific settings diff --git a/packages/dev/commands/build.ts b/packages/dev/commands/build.ts index bbc9f53..06b9dce 100644 --- a/packages/dev/commands/build.ts +++ b/packages/dev/commands/build.ts @@ -171,7 +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" };' : ""} +${opts.hasUserConfigEnabled ? 'import type { UserConfig } from "./config.types.ts";\nimport userConfigData from "../config.json" with { type: "json" };' : ""} ${imports.join("\n")} diff --git a/packages/dev/commands/dev.ts b/packages/dev/commands/dev.ts index cb6395d..6095982 100644 --- a/packages/dev/commands/dev.ts +++ b/packages/dev/commands/dev.ts @@ -13,7 +13,7 @@ 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"; @@ -393,8 +393,7 @@ export function registerDevCommand(cli: CAC) { .on("change", (p) => processFile("change", p)) .on("unlink", (p) => processFile("unlink", p)); - // Watch config.json for changes if userConfig is enabled - let configWatcher: chokidar.FSWatcher | null = null; + let configWatcher: FSWatcher | null = null; if (config.experimental?.userConfig) { const configJsonPath = path.join(root, "config.json"); configWatcher = chokidar.watch(configJsonPath, { diff --git a/packages/dev/commands/generate-config-types.ts b/packages/dev/commands/generate-config-types.ts index 8457ddf..75e7afa 100644 --- a/packages/dev/commands/generate-config-types.ts +++ b/packages/dev/commands/generate-config-types.ts @@ -15,9 +15,10 @@ export function registerGenerateConfigTypesCommand(cli: CAC) { const projectRoot = path.resolve(process.cwd(), options.path); const configJsonPath = path.join(projectRoot, "config.json"); - const outputPath = path.join(projectRoot, "config.types.ts"); + 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( diff --git a/packages/dev/utils/config-type-generator.ts b/packages/dev/utils/config-type-generator.ts index 90bb654..d37ee3c 100644 --- a/packages/dev/utils/config-type-generator.ts +++ b/packages/dev/utils/config-type-generator.ts @@ -88,6 +88,66 @@ export type { UserConfig }; await fs.writeFile(outputPath, fileContent, "utf-8"); } +const DISCORD_D_TS_CONTENT = `import type { UserConfig } from "./config.types"; + +declare module "discord.js" { + interface Client { + config?: UserConfig; + } +} +`; + +const TSCONFIG_INCLUDE_ENTRY = ".djscore/**/*.d.ts"; + +/** + * Creates .djscore/discord.d.ts and ensures tsconfig.json include contains the .djscore types entry. + */ +export async function ensureDiscordAugmentation( + projectRoot: string, + silent = false, +): Promise { + const djscoreDir = path.join(projectRoot, ".djscore"); + const discordDtsPath = path.join(djscoreDir, "discord.d.ts"); + const tsconfigPath = path.join(projectRoot, "tsconfig.json"); + + try { + await fs.mkdir(djscoreDir, { recursive: true }); + await fs.writeFile(discordDtsPath, DISCORD_D_TS_CONTENT.trimStart(), "utf-8"); + } catch (error: unknown) { + if (!silent) { + console.warn( + pc.yellow("⚠️ Could not write .djscore/discord.d.ts"), + error instanceof Error ? error.message : error, + ); + } + return; + } + + try { + const raw = await fs.readFile(tsconfigPath, "utf-8"); + const tsconfig = JSON.parse(raw) as { include?: string[] }; + const include = tsconfig.include; + if (!Array.isArray(include)) { + return; + } + if (include.includes(TSCONFIG_INCLUDE_ENTRY)) { + return; + } + include.push(TSCONFIG_INCLUDE_ENTRY); + tsconfig.include = include; + await fs.writeFile( + tsconfigPath, + JSON.stringify(tsconfig, null, 2), + "utf-8", + ); + if (!silent) { + console.log(pc.green("✓ tsconfig.json include updated for .djscore types")); + } + } catch { + // tsconfig not found or invalid: skip, no need to warn every time + } +} + /** * Auto-generate config types if userConfig is enabled and config.json exists * This is called automatically by dev/build/start commands @@ -97,7 +157,7 @@ export async function autoGenerateConfigTypes( silent = false, ): Promise { const configJsonPath = path.join(projectRoot, "config.json"); - const outputPath = path.join(projectRoot, "config.types.ts"); + const outputPath = path.join(projectRoot, ".djscore", "config.types.ts"); try { await fs.access(configJsonPath); @@ -114,7 +174,9 @@ export async function autoGenerateConfigTypes( } try { + await fs.mkdir(path.dirname(outputPath), { recursive: true }); await generateTypesFromJson(configJsonPath, outputPath); + await ensureDiscordAugmentation(projectRoot, silent); if (!silent) { console.log(pc.green("✓ Config types auto-generated")); } diff --git a/packages/runtime/DjsClient.ts b/packages/runtime/DjsClient.ts index b779404..e05fac5 100644 --- a/packages/runtime/DjsClient.ts +++ b/packages/runtime/DjsClient.ts @@ -36,7 +36,7 @@ export default class DjsClient extends Client { new ApplicationCommandHandler(this); public cronHandler: CronHandler = new CronHandler(this); private readonly djsConfig: Config; - public readonly conf?: UserConfig; + public readonly config?: UserConfig; constructor({ djsConfig, userConfig }: { djsConfig: Config; userConfig?: UserConfig }) { super({ @@ -48,7 +48,7 @@ export default class DjsClient extends Client { ], }); this.djsConfig = djsConfig; - this.conf = userConfig as UserConfig; + this.config = userConfig as UserConfig; if (djsConfig.servers && djsConfig.servers.length > 0) { this.commandsHandler.setGuilds(djsConfig.servers); From 4e41aadde9a27ec865377d27b4be25a0f373040d Mon Sep 17 00:00:00 2001 From: Cleboost Date: Sun, 8 Feb 2026 17:47:51 +0100 Subject: [PATCH 08/10] biome: check --- app/config.json | 2 +- packages/dev/commands/generate-config-types.ts | 5 ++++- packages/dev/utils/common.ts | 2 +- packages/dev/utils/config-type-generator.ts | 14 +++++++++----- packages/runtime/DjsClient.ts | 5 ++++- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/config.json b/app/config.json index 57e30fd..cf4e126 100644 --- a/app/config.json +++ b/app/config.json @@ -1,3 +1,3 @@ { - "apiKeys": ["key1", "key2"] + "apiKeys": ["key1", "key2"] } diff --git a/packages/dev/commands/generate-config-types.ts b/packages/dev/commands/generate-config-types.ts index 75e7afa..7c06520 100644 --- a/packages/dev/commands/generate-config-types.ts +++ b/packages/dev/commands/generate-config-types.ts @@ -7,7 +7,10 @@ import { generateTypesFromJson } from "../utils/config-type-generator"; export function registerGenerateConfigTypesCommand(cli: CAC) { cli - .command("generate-config-types", "Generate TypeScript types from config.json") + .command( + "generate-config-types", + "Generate TypeScript types from config.json", + ) .option("-p, --path ", "Custom project path", { default: "." }) .action(async (options: { path: string }) => { console.log(banner); diff --git a/packages/dev/utils/common.ts b/packages/dev/utils/common.ts index 66d7eef..7aba9a5 100644 --- a/packages/dev/utils/common.ts +++ b/packages/dev/utils/common.ts @@ -143,7 +143,7 @@ export async function runBot(projectPath: string) { console.log(`${pc.green("✓")} Loaded ${pc.bold(tasks.size)} cron tasks`); } - let userConfig: unknown = undefined; + let userConfig: unknown; if (config.experimental?.userConfig) { try { const configJsonPath = path.join(root, "config.json"); diff --git a/packages/dev/utils/config-type-generator.ts b/packages/dev/utils/config-type-generator.ts index d37ee3c..42c339e 100644 --- a/packages/dev/utils/config-type-generator.ts +++ b/packages/dev/utils/config-type-generator.ts @@ -112,7 +112,11 @@ export async function ensureDiscordAugmentation( try { await fs.mkdir(djscoreDir, { recursive: true }); - await fs.writeFile(discordDtsPath, DISCORD_D_TS_CONTENT.trimStart(), "utf-8"); + await fs.writeFile( + discordDtsPath, + DISCORD_D_TS_CONTENT.trimStart(), + "utf-8", + ); } catch (error: unknown) { if (!silent) { console.warn( @@ -141,7 +145,9 @@ export async function ensureDiscordAugmentation( "utf-8", ); if (!silent) { - console.log(pc.green("✓ tsconfig.json include updated for .djscore types")); + console.log( + pc.green("✓ tsconfig.json include updated for .djscore types"), + ); } } catch { // tsconfig not found or invalid: skip, no need to warn every time @@ -184,9 +190,7 @@ export async function autoGenerateConfigTypes( } catch (error: unknown) { if (!silent) { console.warn( - pc.yellow( - `⚠️ Error generating config types from ${configJsonPath}`, - ), + pc.yellow(`⚠️ Error generating config types from ${configJsonPath}`), ); console.warn( pc.dim(" Possible causes: invalid JSON syntax, file permissions"), diff --git a/packages/runtime/DjsClient.ts b/packages/runtime/DjsClient.ts index e05fac5..b3d891c 100644 --- a/packages/runtime/DjsClient.ts +++ b/packages/runtime/DjsClient.ts @@ -38,7 +38,10 @@ export default class DjsClient extends Client { private readonly djsConfig: Config; public readonly config?: UserConfig; - constructor({ djsConfig, userConfig }: { djsConfig: Config; userConfig?: UserConfig }) { + constructor({ + djsConfig, + userConfig, + }: { djsConfig: Config; userConfig?: UserConfig }) { super({ intents: [ IntentsBitField.Flags.Guilds, From d7f88b92553a6a755c4e0f65424d9ed1d85639c5 Mon Sep 17 00:00:00 2001 From: Cleboost Date: Sun, 8 Feb 2026 17:56:18 +0100 Subject: [PATCH 09/10] Add support for client.config managed by djs-core, updating runtime and dev dependencies to minor versions. --- .changeset/cyan-beers-boil.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/cyan-beers-boil.md diff --git a/.changeset/cyan-beers-boil.md b/.changeset/cyan-beers-boil.md new file mode 100644 index 0000000..4977e3f --- /dev/null +++ b/.changeset/cyan-beers-boil.md @@ -0,0 +1,6 @@ +--- +"@djs-core/runtime": minor +"@djs-core/dev": minor +--- + +Add support of client.config (managed by djs-core) From 3ee3abac48d4a26305eebb96c4894d0b006431e4 Mon Sep 17 00:00:00 2001 From: Cleboost Date: Sun, 8 Feb 2026 17:58:19 +0100 Subject: [PATCH 10/10] Update @biomejs/biome to version 2.3.14 in package.json, bun.lock, and biome.json; adjust error handling in common.ts to improve clarity. --- biome.json | 2 +- bun.lock | 20 ++++++++++---------- package.json | 2 +- packages/dev/utils/common.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/biome.json b/biome.json index 4f86130..9807869 100644 --- a/biome.json +++ b/biome.json @@ -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", diff --git a/bun.lock b/bun.lock index 9c1ffe4..8cc4ad7 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "djs-core", "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", @@ -66,23 +66,23 @@ "packages": { "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], - "@biomejs/biome": ["@biomejs/biome@2.3.10", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.10", "@biomejs/cli-darwin-x64": "2.3.10", "@biomejs/cli-linux-arm64": "2.3.10", "@biomejs/cli-linux-arm64-musl": "2.3.10", "@biomejs/cli-linux-x64": "2.3.10", "@biomejs/cli-linux-x64-musl": "2.3.10", "@biomejs/cli-win32-arm64": "2.3.10", "@biomejs/cli-win32-x64": "2.3.10" }, "bin": { "biome": "bin/biome" } }, "sha512-/uWSUd1MHX2fjqNLHNL6zLYWBbrJeG412/8H7ESuK8ewoRoMPUgHDebqKrPTx/5n6f17Xzqc9hdg3MEqA5hXnQ=="], + "@biomejs/biome": ["@biomejs/biome@2.3.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.14", "@biomejs/cli-darwin-x64": "2.3.14", "@biomejs/cli-linux-arm64": "2.3.14", "@biomejs/cli-linux-arm64-musl": "2.3.14", "@biomejs/cli-linux-x64": "2.3.14", "@biomejs/cli-linux-x64-musl": "2.3.14", "@biomejs/cli-win32-arm64": "2.3.14", "@biomejs/cli-win32-x64": "2.3.14" }, "bin": { "biome": "bin/biome" } }, "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M6xUjtCVnNGFfK7HMNKa593nb7fwNm43fq1Mt71kpLpb+4mE7odO8W/oWVDyBVO4ackhresy1ZYO7OJcVo/B7w=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vae7+V6t/Avr8tVbFNjnFSTKZogZHFYl7MMH62P/J1kZtr0tyRQ9Fe0onjqjS2Ek9lmNLmZc/VR5uSekh+p1fg=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-PNkLNQG6RLo8lG7QoWe/hhnMxJIt1tEimoXpGQjwS/dkdNiKBLPv4RpeQl8o3s1OKI3ZOR5XPiYtmbGGHAOnLA=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-hhPw2V3/EpHKsileVOFynuWiKRgFEV48cLe0eA+G2wO4SzlwEhLEB9LhlSrVeu2mtSn205W283LkX7Fh48CaxA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-KT67FKfzIw6DNnUNdYlBg+eU24Go3n75GWK6NwU4+yJmDYFe9i/MjiI+U/iEzKvo0g7G7MZqoyrhIYuND2w8QQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-B9DszIHkuKtOH2IFeeVkQmSMVUjss9KtHaNXquYYWCjH8IstNgXgx5B0aSBQNr6mn4RcKKRQZXn9Zu1rM3O0/A=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-LInRbXhYujtL3sH2TMCH/UBwJZsoGwfQjBrMfl84CD4hL/41C/EU5mldqf1yoFpsI0iPWuU83U+nB2TUUypWeg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-wwAkWD1MR95u+J4LkWP74/vGz+tRrIQvr8kfMMJY8KOQ8+HMVleREOcPYsQX82S7uueco60L58Wc6M1I9WA9Dw=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-ZsZzQsl9U+wxFrGGS4f6UxREUlgHwmEfu1IrXlgNFrNnd5Th6lIJr8KmSzu/+meSa9f4rzFrbEW9LBBA6ScoMA=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QTfHZQh62SDFdYc2nfmZFuTm5yYb4eO1zwfB+90YxUumRCR171tS1GoTX5OD0wrv4UsziMPmrePMtkTnNyYG3g=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-KQU7EkbBBuHPW3/rAcoiVmhlPtDSGOGRPv9js7qJVpYTzjQmVR+C9Rfcz+ti8YCH+zT1J52tuBybtP4IodjxZQ=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-o7lYc9n+CfRbHvkjPhm8s9FgbKdYZu5HCcGVMItLjz93EhgJ8AM44W+QckDqLA9MKDNFrR8nPbO4b73VC5kGGQ=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-+IKYkj/pUBbnRf1G1+RlyA3LWiDgra1xpS7H2g4BuOzzRbRB+hmlw0yFsLprHhbbt7jUzbzAbAjK/Pn0FDnh1A=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-pHEFgq7dUEsKnqG9mx9bXihxGI49X+ar+UBrEIj3Wqj3UCZp1rNgV+OoyjFgcXsjCWpuEAF4VJdkZr3TrWdCbQ=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oizCjdyQ3WJEswpb3Chdngeat56rIdSYK12JI3iI11Mt5T5EXcZ7WLuowzEaFPNJ3zmOQFliMN8QY1Pi+qsfdQ=="], "@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.0.14", "", { "dependencies": { "@changesets/config": "^3.1.2", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA=="], diff --git a/package.json b/package.json index 189ff0e..0f85cf1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/dev/utils/common.ts b/packages/dev/utils/common.ts index 7aba9a5..961c331 100644 --- a/packages/dev/utils/common.ts +++ b/packages/dev/utils/common.ts @@ -150,7 +150,7 @@ export async function runBot(projectPath: string) { const configJsonContent = await fs.readFile(configJsonPath, "utf-8"); userConfig = JSON.parse(configJsonContent); console.log(`${pc.green("✓")} User config loaded`); - } catch (error) { + } catch (_error) { console.warn( pc.yellow( "⚠️ userConfig is enabled but config.json not found or invalid",