diff --git a/apps/test-bot/package.json b/apps/test-bot/package.json index 8ee571c8..11710273 100644 --- a/apps/test-bot/package.json +++ b/apps/test-bot/package.json @@ -21,7 +21,7 @@ "discord.js": "catalog:discordjs", "dotenv": "^16.4.7", "ms": "^2.1.3", - "zod": "^3.25.56" + "zod": "^4.1.1" }, "devDependencies": { "@types/ms": "^2.1.0", diff --git a/apps/test-bot/src/app/commands/(developer)/+middleware.ts b/apps/test-bot/src/app/commands/(developer)/+middleware.ts index ebd06ced..59d060d2 100644 --- a/apps/test-bot/src/app/commands/(developer)/+middleware.ts +++ b/apps/test-bot/src/app/commands/(developer)/+middleware.ts @@ -1,4 +1,4 @@ -import { Logger, MiddlewareContext } from 'commandkit'; +import { Logger, MiddlewareContext, stopMiddlewares } from 'commandkit'; import { MessageFlags } from 'discord.js'; export function beforeExecute(ctx: MiddlewareContext) { @@ -16,7 +16,7 @@ export function beforeExecute(ctx: MiddlewareContext) { ctx.message.reply('You are not allowed to use this command.'); } - ctx.cancel(); + stopMiddlewares(); } } diff --git a/apps/test-bot/src/app/commands/(developer)/run-after.ts b/apps/test-bot/src/app/commands/(developer)/run-after.ts index 81705cb6..30ff3703 100644 --- a/apps/test-bot/src/app/commands/(developer)/run-after.ts +++ b/apps/test-bot/src/app/commands/(developer)/run-after.ts @@ -1,4 +1,8 @@ -import { CommandData, after, ChatInputCommand } from 'commandkit'; +import { + CommandData, + unstable_after as after, + ChatInputCommand, +} from 'commandkit'; export const command: CommandData = { name: 'run-after', diff --git a/apps/test-bot/src/app/commands/(general)/+middleware.ts b/apps/test-bot/src/app/commands/(general)/+middleware.ts index 089016cb..2e086989 100644 --- a/apps/test-bot/src/app/commands/(general)/+middleware.ts +++ b/apps/test-bot/src/app/commands/(general)/+middleware.ts @@ -1,9 +1,10 @@ -import { Logger, MiddlewareContext } from 'commandkit'; +import { Logger, MiddlewareContext, stopMiddlewares } from 'commandkit'; export function beforeExecute(ctx: MiddlewareContext) { Logger.info( `Directory-scoped middleware: ${ctx.commandName} will be executed!`, ); + // stopMiddlewares(); } export function afterExecute(ctx: MiddlewareContext) { diff --git a/apps/test-bot/src/app/commands/(general)/+ping.middleware.ts b/apps/test-bot/src/app/commands/(general)/+ping.middleware.ts index 7f5573c4..facbe09a 100644 --- a/apps/test-bot/src/app/commands/(general)/+ping.middleware.ts +++ b/apps/test-bot/src/app/commands/(general)/+ping.middleware.ts @@ -1,9 +1,25 @@ -import { Logger, MiddlewareContext } from 'commandkit'; +import { + unstable_after as after, + Logger, + type MiddlewareContext, + stopMiddlewares, +} from 'commandkit'; export function beforeExecute(ctx: MiddlewareContext) { + // Logger.info( + // `Command-scoped middleware: ${ctx.commandName} will be executed!`, + // ); + + Logger.info(`Command-scoped middleware: ${ctx.commandName} will be stopped!`); Logger.info( - `Command-scoped middleware: ${ctx.commandName} will be executed!`, + 'None of the other beforeExecute middlewares are supposed to be executed', ); + + after(() => { + Logger.info(`after() has been called in command-scoped middleware: ping`); + }); + + stopMiddlewares(); } export function afterExecute(ctx: MiddlewareContext) { diff --git a/apps/test-bot/src/app/commands/(general)/ping.ts b/apps/test-bot/src/app/commands/(general)/ping.ts index b2814a15..5cb7aca0 100644 --- a/apps/test-bot/src/app/commands/(general)/ping.ts +++ b/apps/test-bot/src/app/commands/(general)/ping.ts @@ -11,6 +11,10 @@ import { AutocompleteCommandContext, CommandMetadata, MessageCommandContext, + stopMiddlewares, + Logger, + unstable_after as after, + CommandMetadataFunction, } from 'commandkit'; export const command: CommandData = { @@ -32,10 +36,25 @@ export const command: CommandData = { // guilds: ['1314834483660455938'], }; -export const metadata: CommandMetadata = { - userPermissions: 'Administrator', - botPermissions: 'KickMembers', - // guilds: ['1314834483660455938'], +// export const metadata: CommandMetadata = { +// // userPermissions: 'Administrator', +// // botPermissions: 'KickMembers', +// // guilds: ['1314834483660455938'], +// aliases: [''], +// userPermissions: 'Administrator', +// botPermissions: 'KickMembers', +// guilds: ['1314834483660455938'], +// }; + +export const generateMetadata: CommandMetadataFunction = async () => { + // Dynamically determine the metadata for the command + + return { + userPermissions: 'Administrator', + botPermissions: ['KickMembers', 'BanMembers'], + // guilds: ['1234567890', '1234567891'], + aliases: ['p', 'pong'], + }; }; const tests = Array.from({ length: 10 }, (_, i) => ({ @@ -98,4 +117,8 @@ export async function chatInput({ button.setDisabled(true); message.edit({ components: [row] }); }); + + after(() => { + Logger.debug('after called in ping'); + }); } diff --git a/apps/test-bot/src/app/commands/(interactions)/+middleware.ts b/apps/test-bot/src/app/commands/(interactions)/+middleware.ts index 5521f325..f12a33d9 100644 --- a/apps/test-bot/src/app/commands/(interactions)/+middleware.ts +++ b/apps/test-bot/src/app/commands/(interactions)/+middleware.ts @@ -1,4 +1,4 @@ -import { Logger, MiddlewareContext } from 'commandkit'; +import { Logger, MiddlewareContext, stopMiddlewares } from 'commandkit'; import { MessageFlags } from 'discord.js'; export function beforeExecute(ctx: MiddlewareContext) { @@ -18,7 +18,7 @@ export function beforeExecute(ctx: MiddlewareContext) { ctx.message.reply('You are not allowed to use this command.'); } - ctx.cancel(); + stopMiddlewares(); } } diff --git a/apps/test-bot/src/app/commands/(interactions)/confirmation.tsx b/apps/test-bot/src/app/commands/(interactions)/confirmation.tsx index 34988285..d19abbd2 100644 --- a/apps/test-bot/src/app/commands/(interactions)/confirmation.tsx +++ b/apps/test-bot/src/app/commands/(interactions)/confirmation.tsx @@ -6,7 +6,7 @@ import { OnButtonKitClick, } from 'commandkit'; import { ButtonStyle, MessageFlags } from 'discord.js'; -import { AiConfig, AiCommand } from '@commandkit/ai'; +import { AiConfig, AiCommand, ToolParameterType } from '@commandkit/ai'; import { z } from 'zod'; export const command: CommandData = { @@ -15,7 +15,7 @@ export const command: CommandData = { }; export const aiConfig = { - parameters: z.object({ + inputSchema: z.object({ message: z .string() .describe('The message to be shown in the confirmation.'), diff --git a/apps/test-bot/src/app/commands/(leveling)/xp.ts b/apps/test-bot/src/app/commands/(leveling)/xp.ts index 441e03c1..09556930 100644 --- a/apps/test-bot/src/app/commands/(leveling)/xp.ts +++ b/apps/test-bot/src/app/commands/(leveling)/xp.ts @@ -1,5 +1,5 @@ import { ChatInputCommandContext, CommandData } from 'commandkit'; -import { database } from '@/database/store.ts'; +import { database } from '@/database/store'; import { cacheTag } from '@commandkit/cache'; import { AiCommand, AiConfig } from '@commandkit/ai'; import { z } from 'zod'; @@ -11,7 +11,7 @@ export const command: CommandData = { export const aiConfig = { description: 'Get the XP of a user in a guild.', - parameters: z.object({ + inputSchema: z.object({ guildId: z.string().describe('The ID of the guild.'), userId: z.string().describe('The ID of the user.'), }), diff --git a/apps/test-bot/src/app/commands/+global-middleware.ts b/apps/test-bot/src/app/commands/+global-middleware.ts index f0e02933..04b04573 100644 --- a/apps/test-bot/src/app/commands/+global-middleware.ts +++ b/apps/test-bot/src/app/commands/+global-middleware.ts @@ -1,7 +1,9 @@ -import { Logger, MiddlewareContext } from 'commandkit'; +import { Logger, MiddlewareContext, stopMiddlewares } from 'commandkit'; export function beforeExecute(ctx: MiddlewareContext) { Logger.info(`Global middleware: ${ctx.commandName} will be executed!`); + // ctx.message.reply('Global middleware: This command will not be executed!'); + // stopMiddlewares(); } export function afterExecute(ctx: MiddlewareContext) { diff --git a/apps/website/docs/api-reference/ai/functions/create-tool.mdx b/apps/website/docs/api-reference/ai/functions/create-tool.mdx index b853c1bd..e3d0f88d 100644 --- a/apps/website/docs/api-reference/ai/functions/create-tool.mdx +++ b/apps/website/docs/api-reference/ai/functions/create-tool.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## createTool - + Creates a new AI tool with the specified configuration. This function wraps the underlying AI library's tool creation with additional diff --git a/apps/website/docs/api-reference/ai/interfaces/configure-ai.mdx b/apps/website/docs/api-reference/ai/interfaces/configure-ai.mdx index 1f31e2d9..23d77e27 100644 --- a/apps/website/docs/api-reference/ai/interfaces/configure-ai.mdx +++ b/apps/website/docs/api-reference/ai/interfaces/configure-ai.mdx @@ -23,16 +23,16 @@ interface ConfigureAI { messageFilter?: MessageFilter; selectAiModel: SelectAiModel; prepareSystemPrompt?: (ctx: AiContext, message: Message) => Promise; - preparePrompt?: ( - ctx: AiContext, - message: Message, + preparePrompt?: ( + ctx: AiContext, + message: Message, ) => Promise; onProcessingStart?: (ctx: AiContext, message: Message) => Promise; onProcessingFinish?: (ctx: AiContext, message: Message) => Promise; - onResult?: ( - ctx: AiContext, - message: Message, - result: AIGenerateResult, + onResult?: ( + ctx: AiContext, + message: Message, + result: AIGenerateResult, ) => Promise; onError?: (ctx: AiContext, message: Message, error: Error) => Promise; } @@ -49,24 +49,24 @@ Whether to disable the built-in tools. Default is false. MessageFilter`} /> -A filter function that determines whether a message should be processed by the AI. +A filter function that determines whether a message should be processed by the AI. CommandKit invokes this function before processing the message. ### selectAiModel SelectAiModel`} /> -A function that selects the AI model to use based on the message. +A function that selects the AI model to use based on the message. This function should return a promise that resolves to an object containing the model and options. ### prepareSystemPrompt AiContext, message: Message) => Promise<string>`} /> -A function that generates a system prompt based on the message. -This function should return a promise that resolves to a string containing the system prompt. +A function that generates a system prompt based on the message. +This function should return a promise that resolves to a string containing the system prompt. If not provided, a default system prompt will be used. ### preparePrompt -AiContext, message: Message, ) => Promise<string | AiMessage>`} /> +AiContext, message: Message, ) => Promise<string | AiMessage>`} /> A function that prepares the prompt for the AI model. ### onProcessingStart @@ -81,7 +81,7 @@ A function that gets called when the AI starts processing a message. A function that gets called when the AI finishes processing a message. ### onResult -AiContext, message: Message, result: AIGenerateResult, ) => Promise<void>`} /> +AiContext, message: Message, result: AIGenerateResult, ) => Promise<void>`} /> A function that gets called upon receiving the result from the AI model. ### onError diff --git a/apps/website/docs/api-reference/ai/interfaces/create-tool-options.mdx b/apps/website/docs/api-reference/ai/interfaces/create-tool-options.mdx index 95530697..8b6a6b4a 100644 --- a/apps/website/docs/api-reference/ai/interfaces/create-tool-options.mdx +++ b/apps/website/docs/api-reference/ai/interfaces/create-tool-options.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## CreateToolOptions - + Configuration options for creating an AI tool. diff --git a/apps/website/docs/api-reference/ai/types/infer-parameters.mdx b/apps/website/docs/api-reference/ai/types/infer-parameters.mdx index 245777c3..f2b4fa43 100644 --- a/apps/website/docs/api-reference/ai/types/infer-parameters.mdx +++ b/apps/website/docs/api-reference/ai/types/infer-parameters.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## InferParameters - + Utility type that infers the TypeScript type from a tool parameter schema. Supports both Zod schemas and AI library schemas. @@ -23,5 +23,7 @@ type InferParameters = T extends Schema ? T['_type'] : T extends z.ZodTypeAny ? z.infer - : never + : T extends z3.ZodTypeAny + ? z3.infer + : never ``` diff --git a/apps/website/docs/api-reference/ai/types/tool-execute-function.mdx b/apps/website/docs/api-reference/ai/types/tool-execute-function.mdx index 18fbe1eb..2b716410 100644 --- a/apps/website/docs/api-reference/ai/types/tool-execute-function.mdx +++ b/apps/website/docs/api-reference/ai/types/tool-execute-function.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## ToolExecuteFunction - + Type definition for a tool's execute function. diff --git a/apps/website/docs/api-reference/ai/types/tool-parameter-type.mdx b/apps/website/docs/api-reference/ai/types/tool-parameter-type.mdx index 3668869c..f19b3ea0 100644 --- a/apps/website/docs/api-reference/ai/types/tool-parameter-type.mdx +++ b/apps/website/docs/api-reference/ai/types/tool-parameter-type.mdx @@ -13,11 +13,11 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## ToolParameterType - + Type representing the parameters schema for AI tools. Extracted from the first parameter of the `tool` function from the 'ai' library. ```ts title="Signature" -type ToolParameterType = z.ZodType | Schema +type ToolParameterType = z.ZodType | z3.ZodType | Schema ``` diff --git a/apps/website/docs/api-reference/cache/classes/use-cache-directive-plugin.mdx b/apps/website/docs/api-reference/cache/classes/use-cache-directive-plugin.mdx index 742d0a44..66fad48f 100644 --- a/apps/website/docs/api-reference/cache/classes/use-cache-directive-plugin.mdx +++ b/apps/website/docs/api-reference/cache/classes/use-cache-directive-plugin.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## UseCacheDirectivePlugin - + Compiler plugin for the "use cache" directive. This plugin transforms the "use cache" directive into a runtime cache operation. diff --git a/apps/website/docs/api-reference/commandkit/classes/analytics-engine.mdx b/apps/website/docs/api-reference/commandkit/classes/analytics-engine.mdx index 8ac66f46..fad5b6b7 100644 --- a/apps/website/docs/api-reference/commandkit/classes/analytics-engine.mdx +++ b/apps/website/docs/api-reference/commandkit/classes/analytics-engine.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## AnalyticsEngine - + AnalyticsEngine class for managing analytics providers and tracking events. This class allows you to register a provider, set a filter for events, and track or identify events. diff --git a/apps/website/docs/api-reference/commandkit/classes/context.mdx b/apps/website/docs/api-reference/commandkit/classes/context.mdx index b379d969..99947654 100644 --- a/apps/website/docs/api-reference/commandkit/classes/context.mdx +++ b/apps/website/docs/api-reference/commandkit/classes/context.mdx @@ -49,7 +49,6 @@ class Context>) => Context; isMiddleware() => this is MiddlewareContext; args() => string[]; - exit() => never; } ``` @@ -201,14 +200,6 @@ Checks if this context is a middleware context. string[]`} /> Gets the command arguments (only available for message commands). -### exit - - never`} /> - -Stops upcoming middleware or current command execution. -If this is called inside pre-stage middleware, the next run will be the actual command, skipping all other pre-stage middlewares. -If this is called inside a command itself, it will skip all post-stage middlewares. -If this is called inside post-stage middleware, it will skip all other post-stage middlewares. diff --git a/apps/website/docs/api-reference/commandkit/classes/middleware-context.mdx b/apps/website/docs/api-reference/commandkit/classes/middleware-context.mdx index 6e0fb33a..c9b08166 100644 --- a/apps/website/docs/api-reference/commandkit/classes/middleware-context.mdx +++ b/apps/website/docs/api-reference/commandkit/classes/middleware-context.mdx @@ -19,8 +19,6 @@ Extended context class for middleware execution with additional control methods. ```ts title="Signature" class MiddlewareContext extends Context { - cancelled: boolean - cancel() => void; setCommandRunner(fn: RunCommand) => void; } ``` @@ -30,16 +28,6 @@ class MiddlewareContext e
-### cancelled - - - -Whether the command execution was cancelled. -### cancel - - void`} /> - -Cancels the command execution. ### setCommandRunner RunCommand) => void`} /> diff --git a/apps/website/docs/api-reference/commandkit/functions/exit-middleware.mdx b/apps/website/docs/api-reference/commandkit/functions/exit-middleware.mdx deleted file mode 100644 index 65882d98..00000000 --- a/apps/website/docs/api-reference/commandkit/functions/exit-middleware.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "ExitMiddleware" -isDefaultIndex: false -generated: true ---- - -import MemberInfo from '@site/src/components/MemberInfo'; -import GenerationInfo from '@site/src/components/GenerationInfo'; -import MemberDescription from '@site/src/components/MemberDescription'; - - - - -## exitMiddleware - - - -Cancel upcoming middleware execution. -If this is called inside pre-stage middleware, the next run will be the actual command, skipping all other pre-stage middlewares. -If this is called inside a command itself, it will skip all post-stage middlewares. -If this is called inside post-stage middleware, it will skip all other post-stage middlewares. - -```ts title="Signature" -function exitMiddleware(): never -``` diff --git a/apps/website/docs/api-reference/commandkit/functions/flag.mdx b/apps/website/docs/api-reference/commandkit/functions/flag.mdx index f9bc3858..e5b436d7 100644 --- a/apps/website/docs/api-reference/commandkit/functions/flag.mdx +++ b/apps/website/docs/api-reference/commandkit/functions/flag.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## flag - + Create a new feature flag. diff --git a/apps/website/docs/api-reference/commandkit/functions/get-command-kit.mdx b/apps/website/docs/api-reference/commandkit/functions/get-command-kit.mdx index 6e147f6b..9823dad1 100644 --- a/apps/website/docs/api-reference/commandkit/functions/get-command-kit.mdx +++ b/apps/website/docs/api-reference/commandkit/functions/get-command-kit.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## getCommandKit - + diff --git a/apps/website/docs/api-reference/commandkit/functions/redirect.mdx b/apps/website/docs/api-reference/commandkit/functions/redirect.mdx index b7a91a51..64fdd3d6 100644 --- a/apps/website/docs/api-reference/commandkit/functions/redirect.mdx +++ b/apps/website/docs/api-reference/commandkit/functions/redirect.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## redirect - + Stops current command assuming it has been redirected to another command. diff --git a/apps/website/docs/api-reference/commandkit/functions/rethrow.mdx b/apps/website/docs/api-reference/commandkit/functions/rethrow.mdx index 60ab12d1..8993ab6f 100644 --- a/apps/website/docs/api-reference/commandkit/functions/rethrow.mdx +++ b/apps/website/docs/api-reference/commandkit/functions/rethrow.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## rethrow - + Rethrow the error if it is a CommandKit error. diff --git a/apps/website/docs/api-reference/commandkit/functions/stop-events.mdx b/apps/website/docs/api-reference/commandkit/functions/stop-events.mdx index 2e7994c8..0d0d58b9 100644 --- a/apps/website/docs/api-reference/commandkit/functions/stop-events.mdx +++ b/apps/website/docs/api-reference/commandkit/functions/stop-events.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## stopEvents - + Stops event propagation. This function should be called inside an event handler to prevent further event handling. diff --git a/apps/website/docs/api-reference/commandkit/functions/stop-middlewares.mdx b/apps/website/docs/api-reference/commandkit/functions/stop-middlewares.mdx new file mode 100644 index 00000000..759c5b95 --- /dev/null +++ b/apps/website/docs/api-reference/commandkit/functions/stop-middlewares.mdx @@ -0,0 +1,22 @@ +--- +title: "StopMiddlewares" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + + + +## stopMiddlewares + + + +Stop upcoming middlewares and command execution. + +```ts title="Signature" +function stopMiddlewares(): never +``` diff --git a/apps/website/docs/api-reference/commandkit/functions/use-environment.mdx b/apps/website/docs/api-reference/commandkit/functions/use-environment.mdx index ce6df1b0..c052206e 100644 --- a/apps/website/docs/api-reference/commandkit/functions/use-environment.mdx +++ b/apps/website/docs/api-reference/commandkit/functions/use-environment.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## useEnvironment - + Use current commandkit context. Throws an error if no context is found. diff --git a/apps/website/docs/api-reference/commandkit/interfaces/command-flag-context.mdx b/apps/website/docs/api-reference/commandkit/interfaces/command-flag-context.mdx index 2fec6a23..5af9d70a 100644 --- a/apps/website/docs/api-reference/commandkit/interfaces/command-flag-context.mdx +++ b/apps/website/docs/api-reference/commandkit/interfaces/command-flag-context.mdx @@ -21,34 +21,34 @@ Context for evaluating command flags in CommandKit. interface CommandFlagContext { client: Client; commandkit: CommandKit; - command: { - /** - * The interaction object if the command was invoked via an interaction. - * This can be a ChatInputCommandInteraction, AutocompleteInteraction, or ContextMenuCommandInteraction. - */ - interaction?: - | ChatInputCommandInteraction - | AutocompleteInteraction - | ContextMenuCommandInteraction; - /** - * The message object if the command was invoked via a message. - */ - message?: Message; - /** - * The guild where the command was invoked, if applicable. - * This will be null for commands invoked in DMs. - */ - guild: Guild | null; - /** - * The channel where the command was invoked. - * This can be a text channel, DM channel, or any other type of text-based channel. - */ - channel: TextBasedChannel | null; - /** - * The loaded command instance that is being executed. - * This contains the command's metadata and logic. - */ - command: LoadedCommand; + command: { + /** + * The interaction object if the command was invoked via an interaction. + * This can be a ChatInputCommandInteraction, AutocompleteInteraction, or ContextMenuCommandInteraction. + */ + interaction?: + | ChatInputCommandInteraction + | AutocompleteInteraction + | ContextMenuCommandInteraction; + /** + * The message object if the command was invoked via a message. + */ + message?: Message; + /** + * The guild where the command was invoked, if applicable. + * This will be null for commands invoked in DMs. + */ + guild: Guild | null; + /** + * The channel where the command was invoked. + * This can be a text channel, DM channel, or any other type of text-based channel. + */ + channel: TextBasedChannel | null; + /** + * The loaded command instance that is being executed. + * This contains the command's metadata and logic. + */ + command: LoadedCommand; }; event: null; } @@ -60,25 +60,25 @@ interface CommandFlagContext { -The Discord client instance. +The Discord client instance. This is the main entry point for interacting with the Discord API. ### commandkit CommandKit`} /> -The CommandKit instance, which provides access to the command framework. +The CommandKit instance, which provides access to the command framework. This includes commands, events, and other features of CommandKit. ### command -LoadedCommand; }`} /> +LoadedCommand; }`} /> -The command context, which includes information about the command being executed. +The command context, which includes information about the command being executed. This can include the interaction, message, guild, channel, and the loaded command. ### event -The event context is null for command flags, as they are not tied to a specific event. +The event context is null for command flags, as they are not tied to a specific event. This is used to differentiate between command and event flags. diff --git a/apps/website/docs/api-reference/commandkit/interfaces/context-parameters.mdx b/apps/website/docs/api-reference/commandkit/interfaces/context-parameters.mdx index 055f9c5a..d5c2939f 100644 --- a/apps/website/docs/api-reference/commandkit/interfaces/context-parameters.mdx +++ b/apps/website/docs/api-reference/commandkit/interfaces/context-parameters.mdx @@ -22,14 +22,14 @@ interface ContextParameters + ### message diff --git a/apps/website/docs/api-reference/commandkit/interfaces/event-flag-context.mdx b/apps/website/docs/api-reference/commandkit/interfaces/event-flag-context.mdx index be20fb31..11b19d7e 100644 --- a/apps/website/docs/api-reference/commandkit/interfaces/event-flag-context.mdx +++ b/apps/website/docs/api-reference/commandkit/interfaces/event-flag-context.mdx @@ -21,35 +21,35 @@ Context for evaluating event flags in CommandKit. interface EventFlagContext { client: Client; commandkit: CommandKit; - event: { - /** - * The parsed event data, which contains the raw data from the event. - * This can include information like user IDs, channel IDs, and other relevant data. - */ - data: ParsedEvent; - /** - * The name of the event being processed. - * This is the string identifier for the event, such as 'messageCreate' or 'guildMemberAdd'. - */ - event: string; - /** - * The namespace of the event, if applicable. - * This can be used to group related events or commands together. - * It is null if the event does not belong to a specific namespace. - */ - namespace: string | null; - /** - * The arguments passed to the event handler. - * This is an array of arguments that were passed when the event was triggered. - * It can be used to access specific data related to the event. - */ - arguments: any[]; - /** - * A function to retrieve the arguments for a specific event type. - * This allows for type-safe access to the arguments based on the event name. - * @param event - The name of the event to retrieve arguments for. - */ - argumentsAs(event: E): ClientEvents[E]; + event: { + /** + * The parsed event data, which contains the raw data from the event. + * This can include information like user IDs, channel IDs, and other relevant data. + */ + data: ParsedEvent; + /** + * The name of the event being processed. + * This is the string identifier for the event, such as 'messageCreate' or 'guildMemberAdd'. + */ + event: string; + /** + * The namespace of the event, if applicable. + * This can be used to group related events or commands together. + * It is null if the event does not belong to a specific namespace. + */ + namespace: string | null; + /** + * The arguments passed to the event handler. + * This is an array of arguments that were passed when the event was triggered. + * It can be used to access specific data related to the event. + */ + arguments: any[]; + /** + * A function to retrieve the arguments for a specific event type. + * This allows for type-safe access to the arguments based on the event name. + * @param event - The name of the event to retrieve arguments for. + */ + argumentsAs(event: E): ClientEvents[E]; }; command: null; } @@ -61,25 +61,25 @@ interface EventFlagContext { -The Discord client instance. +The Discord client instance. This is the main entry point for interacting with the Discord API. ### commandkit CommandKit`} /> -The CommandKit instance, which provides access to the command framework. +The CommandKit instance, which provides access to the command framework. This includes commands, events, and other features of CommandKit. ### event -ParsedEvent; /** * The name of the event being processed. * This is the string identifier for the event, such as 'messageCreate' or 'guildMemberAdd'. */ event: string; /** * The namespace of the event, if applicable. * This can be used to group related events or commands together. * It is null if the event does not belong to a specific namespace. */ namespace: string | null; /** * The arguments passed to the event handler. * This is an array of arguments that were passed when the event was triggered. * It can be used to access specific data related to the event. */ arguments: any[]; /** * A function to retrieve the arguments for a specific event type. * This allows for type-safe access to the arguments based on the event name. * @param event - The name of the event to retrieve arguments for. */ argumentsAs<E extends keyof ClientEvents>(event: E): ClientEvents[E]; }`} /> +ParsedEvent; /** * The name of the event being processed. * This is the string identifier for the event, such as 'messageCreate' or 'guildMemberAdd'. */ event: string; /** * The namespace of the event, if applicable. * This can be used to group related events or commands together. * It is null if the event does not belong to a specific namespace. */ namespace: string | null; /** * The arguments passed to the event handler. * This is an array of arguments that were passed when the event was triggered. * It can be used to access specific data related to the event. */ arguments: any[]; /** * A function to retrieve the arguments for a specific event type. * This allows for type-safe access to the arguments based on the event name. * @param event - The name of the event to retrieve arguments for. */ argumentsAs<E extends keyof ClientEvents>(event: E): ClientEvents[E]; }`} /> -The event context, which includes information about the event being processed. +The event context, which includes information about the event being processed. This can include the parsed event data, the event name, and the namespace if applicable. ### command -The command context is null for event flags, as they are not tied to a specific command. +The command context is null for event flags, as they are not tied to a specific command. This is used to differentiate between command and event flags. diff --git a/apps/website/docs/api-reference/commandkit/types/filter-function.mdx b/apps/website/docs/api-reference/commandkit/types/filter-function.mdx index 17409dc9..0b7aae9f 100644 --- a/apps/website/docs/api-reference/commandkit/types/filter-function.mdx +++ b/apps/website/docs/api-reference/commandkit/types/filter-function.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## FilterFunction - + Filter function type for analytics events. diff --git a/apps/website/docs/api-reference/commandkit/variables/middleware-id.mdx b/apps/website/docs/api-reference/commandkit/variables/middleware-id.mdx index 90b18e61..1873c9ea 100644 --- a/apps/website/docs/api-reference/commandkit/variables/middleware-id.mdx +++ b/apps/website/docs/api-reference/commandkit/variables/middleware-id.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## middlewareId - + diff --git a/apps/website/docs/api-reference/legacy/classes/legacy-handler-plugin.mdx b/apps/website/docs/api-reference/legacy/classes/legacy-handler-plugin.mdx index c2a6f4c8..fffff894 100644 --- a/apps/website/docs/api-reference/legacy/classes/legacy-handler-plugin.mdx +++ b/apps/website/docs/api-reference/legacy/classes/legacy-handler-plugin.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## LegacyHandlerPlugin - + diff --git a/apps/website/docs/api-reference/legacy/interfaces/legacy-handler-plugin-options.mdx b/apps/website/docs/api-reference/legacy/interfaces/legacy-handler-plugin-options.mdx index f52777ac..04505ff7 100644 --- a/apps/website/docs/api-reference/legacy/interfaces/legacy-handler-plugin-options.mdx +++ b/apps/website/docs/api-reference/legacy/interfaces/legacy-handler-plugin-options.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## LegacyHandlerPluginOptions - + Options for the LegacyHandlerPlugin. diff --git a/apps/website/docs/guide/01-getting-started/02-setup-commandkit.mdx b/apps/website/docs/guide/01-getting-started/02-setup-commandkit.mdx index 25c7a724..e5289cda 100644 --- a/apps/website/docs/guide/01-getting-started/02-setup-commandkit.mdx +++ b/apps/website/docs/guide/01-getting-started/02-setup-commandkit.mdx @@ -36,7 +36,7 @@ that looks something like this: │ │ ├── commands/ │ │ │ └── ping.ts │ │ └── events/ -│ │ └── ready/ +│ │ └── clientReady/ │ │ └── log.ts │ └── app.ts ├── .env diff --git a/apps/website/docs/guide/02-commands/01-chat-input-commands.mdx b/apps/website/docs/guide/02-commands/01-chat-input-commands.mdx index 674a984b..7f4bb3ae 100644 --- a/apps/website/docs/guide/02-commands/01-chat-input-commands.mdx +++ b/apps/website/docs/guide/02-commands/01-chat-input-commands.mdx @@ -49,19 +49,3 @@ supports. Learn more about [context menu commands](./03-context-menu-commands.mdx). ::: - -## Guild-based commands - -You can register your chat input command to specific guilds by using -the `guilds` property in the `command` object which accepts an array -of guild IDs. - -```ts title="src/app/commands/ping.ts" {6} -import type { CommandData } from 'commandkit'; - -export const command: CommandData = { - name: 'ping', - description: 'Replies with Pong!', - guilds: ['1055188344188973066'], -}; -``` diff --git a/apps/website/docs/guide/02-commands/05-command-metadata.mdx b/apps/website/docs/guide/02-commands/05-command-metadata.mdx new file mode 100644 index 00000000..b941c5db --- /dev/null +++ b/apps/website/docs/guide/02-commands/05-command-metadata.mdx @@ -0,0 +1,98 @@ +--- +title: Command Metadata +--- + +Command metadata is a way to add additional information to your +commands that CommandKit will handle. + +To get started, you can export a `metadata` object from your command: + +```ts title="src/app/commands/ping.ts" {8-10} +import type { CommandData, CommandMetadata } from 'commandkit'; + +export const command: CommandData = { + name: 'ping', + description: 'Replies with Pong!', +}; + +export const metadata: CommandMetadata = { + // Add your metadata here +}; +``` + +## Metadata properties + +### `userPermissions` + +This is a string, or array of user permission strings that will be +required by the person executing the command. + +```ts title="src/app/commands/ping.ts" +export const metadata: CommandMetadata = { + // If the user does not have the Administrator permission, CommandKit will let them know + userPermissions: 'Administrator', +}; +``` + +### `botPermissions` + +This is a string, or array of bot permission strings that will be +required by your bot to execute the command. This is useful for +commands where your bot needs to have certain permissions in a guild +e.g. moderation commands. + +```ts title="src/app/commands/ping.ts" +export const metadata: CommandMetadata = { + // If the bot does not have these permissions, CommandKit will let them know + botPermissions: ['KickMembers', 'BanMembers'], +}; +``` + +### `guilds` + +This is an array of guild IDs that the command will be registered in, +or be available to be executed (message commands). + +```ts title="src/app/commands/ping.ts" +export const metadata: CommandMetadata = { + guilds: ['1234567890', '1234567891'], +}; +``` + +### `aliases` + +This is an array of alternative command names that will be available +for users to use to execute the command. + +:::warning + +This only works for [message commands](./04-message-commands.mdx). + +::: + +```ts title="src/app/commands/ping.ts" +export const metadata: CommandMetadata = { + aliases: ['p', 'pong'], +}; +``` + +## Generated metadata + +If you'd like to generate metadata dynamically, you can export a +`generateMetadata` function from your command file that should return +a `CommandMetadata` object. + +```ts title="src/app/commands/ping.ts" +import type { CommandMetadataFunction } from 'commandkit'; + +export const generateMetadata: CommandMetadataFunction = async () => { + // Dynamically determine the metadata for the command + + return { + userPermissions: 'Administrator', + botPermissions: ['KickMembers', 'BanMembers'], + guilds: ['1234567890', '1234567891'], + aliases: ['p', 'pong'], + }; +}; +``` diff --git a/apps/website/docs/guide/02-commands/05-after-function.mdx b/apps/website/docs/guide/02-commands/06-after-function.mdx similarity index 50% rename from apps/website/docs/guide/02-commands/05-after-function.mdx rename to apps/website/docs/guide/02-commands/06-after-function.mdx index 71571101..210a872d 100644 --- a/apps/website/docs/guide/02-commands/05-after-function.mdx +++ b/apps/website/docs/guide/02-commands/06-after-function.mdx @@ -2,6 +2,13 @@ title: after Function --- +:::warning UNSTABLE + +The `after()` function is currently marked as unstable and may change +in the future. + +::: + The `after()` function allows you to execute a callback after a command has been executed. This is useful for performing actions that should occur after the command has completed, such as logging or @@ -10,13 +17,18 @@ cleanup tasks. ## Usage ```ts title="src/app/commands/ping.ts" -import { type CommandData, type ChatInputCommand, after } from 'commandkit'; +import { + type CommandData, + type ChatInputCommand, + unstable_after as after, +} from 'commandkit'; export const command: CommandData = {}; export const chatInput: ChatInputCommand = async (ctx) => { after(() => { // This code will be executed after the command has been executed + // Perform any cleanup here console.log('Command has been executed'); }); @@ -33,3 +45,25 @@ The `after()` function will always be called, regardless of whether the command execution was successful or not. ::: + +## Cancelling the after function + +You can cancel the after function by calling the `cancelAfter` +function with the ID of the after function. + +```ts title="src/app/commands/ping.ts" +import { + unstable_after as after, + unstable_cancelAfter as cancelAfter, +} from 'commandkit'; + +export const chatInput: ChatInputCommand = async (ctx) => { + const id = after(() => { + console.log('This will run after the command has finished executing.'); + }); + + if (something) { + cancelAfter(id); + } +}; +``` diff --git a/apps/website/docs/guide/02-commands/06-category-directory.mdx b/apps/website/docs/guide/02-commands/07-category-directory.mdx similarity index 97% rename from apps/website/docs/guide/02-commands/06-category-directory.mdx rename to apps/website/docs/guide/02-commands/07-category-directory.mdx index e727384c..587f84ed 100644 --- a/apps/website/docs/guide/02-commands/06-category-directory.mdx +++ b/apps/website/docs/guide/02-commands/07-category-directory.mdx @@ -31,7 +31,7 @@ plugins, or even other commands through your `commandkit` instance. │ │ │ ├── kick.ts │ │ │ └── ban.ts │ │ └── events/ -│ │ └── ready/ +│ │ └── clientReady/ │ │ └── log.ts │ └── app.ts ├── .env diff --git a/apps/website/docs/guide/02-commands/07-middlewares.mdx b/apps/website/docs/guide/02-commands/08-middlewares.mdx similarity index 70% rename from apps/website/docs/guide/02-commands/07-middlewares.mdx rename to apps/website/docs/guide/02-commands/08-middlewares.mdx index d1466807..c67bbe3a 100644 --- a/apps/website/docs/guide/02-commands/07-middlewares.mdx +++ b/apps/website/docs/guide/02-commands/08-middlewares.mdx @@ -34,8 +34,8 @@ export function afterExecute(ctx: MiddlewareContext) { ## Stop command execution -You can stop a command from running by returning `ctx.cancel()` in the -`beforeExecute` function. +You can stop a command from running by calling `stopMiddlewares()` in +the `beforeExecute` function. ```ts title="src/app/commands/+middleware.ts" import type { MiddlewareContext } from 'commandkit'; @@ -44,7 +44,7 @@ export function beforeExecute(ctx: MiddlewareContext) { if (ctx.interaction.user.id !== '1234567890') { // Conditionally stop command execution console.log(`${ctx.commandName} will not be executed!`); - return ctx.cancel(); + stopMiddlewares(); } // Continue with command execution @@ -52,6 +52,49 @@ export function beforeExecute(ctx: MiddlewareContext) { } ``` +:::tip + +You can also use `stopMiddlewares()` inside a command function to stop +any `afterExecute` middlewares from running. + +In addition, you can also use `stopMiddlewares()` inside any +`afterExecute` middleware function to stop any remaining middlewares +from running. + +::: + +:::warning + +Calling `stopMiddlewares()` in a try/catch block may lead to +unexpected behavior. + +If you still want to use `stopMiddlewares()` in a try/catch block, you +can use the `isErrorType` function to check if the error is an +instance of the `CommandKitErrorCodes.StopMiddlewares` error. + +```ts title="src/app/commands/+middleware.ts" +import type { MiddlewareContext } from 'commandkit'; + +export function beforeExecute(ctx: MiddlewareContext) { + try { + // code that may throw an error + + stopMiddlewares(); // conditionally stop the middleware chain + } catch (error) { + if (isErrorType(error, CommandKitErrorCodes.StopMiddlewares)) { + // if stopMiddlewares() is called in the try block, throw it so CommandKit can stop the middleware chain + throw error; + } + + // this means that the code threw the error, and stopMiddlewares() was not called + // the rest of the middlewares will be executed as normal + console.error(error); + } +} +``` + +::: + ## Middleware types ### Directory-scoped middleware diff --git a/apps/website/docs/guide/03-events/01-discordjs-events.mdx b/apps/website/docs/guide/03-events/01-discordjs-events.mdx index d6c974da..5fa1f86b 100644 --- a/apps/website/docs/guide/03-events/01-discordjs-events.mdx +++ b/apps/website/docs/guide/03-events/01-discordjs-events.mdx @@ -19,7 +19,7 @@ Inside each event directory, you can create multiple handler files that will all execute when that event is triggered. Here's how your project structure might look with the -`"messageCreate"` and `"ready"` events: +`"messageCreate"` and `"clientReady"` events: ```title="" {4-9} . @@ -29,7 +29,7 @@ Here's how your project structure might look with the │ │ ├── messageCreate/ │ │ │ ├── give-xp.ts │ │ │ └── log-message.ts -│ │ └── ready/ +│ │ └── clientReady/ │ │ └── log.ts │ └── app.ts ├── .env @@ -50,12 +50,12 @@ You can see what events are available in Discord.js by checking the To create an event handler, create a folder inside the `src/app/events` directory which should match the event name from -Discord.js. This example will use the `"ready"` event. +Discord.js. This example will use the `"clientReady"` event. -```ts title="src/app/events/ready/log.ts" +```ts title="src/app/events/clientReady/log.ts" import type { EventHandler } from 'commandkit'; -const handler: EventHandler<'ready'> = (client) => { +const handler: EventHandler<'clientReady'> = (client) => { console.log(`🤖 ${client.user.displayName} is online!`); }; @@ -63,11 +63,12 @@ export default handler; ``` That's it! CommandKit will automatically detect this file and register -it as one of the handler functions for the `"ready"` event. +it as one of the handler functions for the `"clientReady"` event. Just like the Discord.js `client.on()` method, the parameters you receive in your event file will be based on what event you're trying -to handle. In the example above, the `"ready"` event should give you a +to handle. In the example above, the `"clientReady"` event should give +you a [`Client`](https://discord.js.org/docs/packages/discord.js/main/Client:Class) instance. @@ -86,12 +87,12 @@ definition. You may want to have some events to only get called once. For this, you can export a variable called `once` from your event function file. -```ts title="src/app/events/ready/log.ts" +```ts title="src/app/events/clientReady/log.ts" import type { EventHandler } from 'commandkit'; export const once = true; -const handler: EventHandler<'ready'> = (client) => { +const handler: EventHandler<'clientReady'> = (client) => { console.log(`🤖 ${client.user.displayName} is online!`); }; diff --git a/apps/website/docs/guide/05-official-plugins/01-commandkit-ai.mdx b/apps/website/docs/guide/05-official-plugins/01-commandkit-ai.mdx index 90e6a814..02f7ac17 100644 --- a/apps/website/docs/guide/05-official-plugins/01-commandkit-ai.mdx +++ b/apps/website/docs/guide/05-official-plugins/01-commandkit-ai.mdx @@ -124,7 +124,7 @@ export const command: CommandData = { }; export const aiConfig: AiConfig = { - parameters: z.object({ + inputSchema: z.object({ username: z.string().describe('The username to greet'), message: z.string().optional().describe('Optional custom greeting message'), }), @@ -282,7 +282,7 @@ import { z } from 'zod'; export const getWeather = createTool({ name: 'getWeather', description: 'Get current weather information for a location', - parameters: z.object({ + inputSchema: z.object({ location: z.string().describe('The city or location to get weather for'), units: z.enum(['celsius', 'fahrenheit']).default('celsius'), }), diff --git a/apps/website/docs/guide/08-advanced/01-setup-commandkit-manually.mdx b/apps/website/docs/guide/08-advanced/01-setup-commandkit-manually.mdx index 5ffa32e6..2a86920b 100644 --- a/apps/website/docs/guide/08-advanced/01-setup-commandkit-manually.mdx +++ b/apps/website/docs/guide/08-advanced/01-setup-commandkit-manually.mdx @@ -108,16 +108,16 @@ Learn more To register and handle events emitted by your discord.js client, create a folder inside the `src/app` directory called `events` and create a folder with the name of the discord.js event you'd like to -handle (e.g. ready, messageCreate, etc). This example will use the -`ready` event. +handle (e.g. clientReady, messageCreate, etc). This example will use +the `clientReady` event. -In the `src/app/events/ready` directory, you can create files which -will export default functions that will be called when the respective -event is emitted by discord.js. Following the `ready` event example -mentioned above, you may want to log when your bot comes online. The -function for that will look like so: +In the `src/app/events/clientReady` directory, you can create files +which will export default functions that will be called when the +respective event is emitted by discord.js. Following the `clientReady` +event example mentioned above, you may want to log when your bot comes +online. The function for that will look like so: -```ts title="src/app/events/log.ts" +```ts title="src/app/events/clientReady/log.ts" import type { Client } from 'discord.js'; export default function (client: Client) { diff --git a/apps/website/docs/guide/08-advanced/04-migrating-from-v0.mdx b/apps/website/docs/guide/08-advanced/04-migrating-from-v0.mdx index 425f9980..a99bb0d2 100644 --- a/apps/website/docs/guide/08-advanced/04-migrating-from-v0.mdx +++ b/apps/website/docs/guide/08-advanced/04-migrating-from-v0.mdx @@ -58,7 +58,7 @@ enables advanced features like automatic route discovery. │ │ ├── commands/ │ │ │ └── ping.ts │ │ └── events/ - │ │ └── ready/ + │ │ └── clientReady/ │ │ └── log.ts │ └── app.ts ├── .env @@ -75,7 +75,7 @@ enables advanced features like automatic route discovery. │ ├── commands/ │ │ └── ping.ts │ ├── events/ - │ │ └── ready/ + │ │ └── clientReady/ │ │ └── log.ts │ └── index.ts ├── .env @@ -241,7 +241,7 @@ control over command execution. ```ts title='src/app/commands/+global-middleware.ts' - import type { MiddlewareContext } from 'commandkit'; + import type { MiddlewareContext, stopMiddlewares } from 'commandkit'; export function beforeExecute(ctx: MiddlewareContext) { // Example: Block command execution based on conditions @@ -249,7 +249,7 @@ control over command execution. ctx.interaction.reply('Access denied: Command execution blocked.'); } - ctx.cancel(); // Prevents command execution + stopMiddlewares(); // Prevents command execution } ``` diff --git a/apps/website/versioned_docs/version-0.1.10/guide/05-event-file-setup.mdx b/apps/website/versioned_docs/version-0.1.10/guide/05-event-file-setup.mdx index de584991..1cd8761b 100644 --- a/apps/website/versioned_docs/version-0.1.10/guide/05-event-file-setup.mdx +++ b/apps/website/versioned_docs/version-0.1.10/guide/05-event-file-setup.mdx @@ -8,25 +8,25 @@ import TabItem from '@theme/TabItem'; # Events Setup -This is a simple overview of how to set up a simple function that is called when the "ready" event is triggered. All this code does is log to the console when the bot is ready. +This is a simple overview of how to set up a simple function that is called when the "clientReady" event is triggered. All this code does is log to the console when the bot is ready. - ```js title="src/events/ready/console-log.js" + ```js title="src/events/clientReady/console-log.js" module.exports = (c, client, handler) => { console.log(`${c.user.username} is ready!`); }; ``` - ```js title="src/events/ready/console-log.js" + ```js title="src/events/clientReady/console-log.js" export default function (c, client, handler) { console.log(`${c.user.username} is ready!`); }; ``` - ```ts title="src/events/ready/console-log.ts" + ```ts title="src/events/clientReady/console-log.ts" import type { Client } from 'discord.js'; import type { CommandKit } from 'commandkit'; @@ -41,7 +41,7 @@ This is a simple overview of how to set up a simple function that is called when ## Parameters explained -The parameters might look a bit confusing at first, but they're actually quite simple. The first parameter `c` is the client object that was returned as a parameter when the "ready" event was triggered. The second parameter `client` is the Discord.js client that was instantiated in your main entry point file. Finally, the `handler` parameter is the current CommandKit instance. +The parameters might look a bit confusing at first, but they're actually quite simple. The first parameter `c` is the client object that was returned as a parameter when the "clientReady" event was triggered. The second parameter `client` is the Discord.js client that was instantiated in your main entry point file. Finally, the `handler` parameter is the current CommandKit instance. To better understand how the parameters work, here's another example but with the "messageCreate" event listener. diff --git a/packages/ai/src/configure.ts b/packages/ai/src/configure.ts index 4673d81b..533b1b57 100644 --- a/packages/ai/src/configure.ts +++ b/packages/ai/src/configure.ts @@ -168,7 +168,7 @@ const AIConfig: Required = { } }, onError: async (_ctx, message, error) => { - Logger.error(`Error processing AI message: ${error}`); + Logger.error`Error processing AI message: ${error}`; const channel = message.channel as TextChannel; if (channel.isSendable()) { @@ -177,7 +177,7 @@ const AIConfig: Required = { content: 'An error occurred while processing your request.', allowedMentions: { parse: [] }, }) - .catch((e) => Logger.error(`Failed to send error message: ${e}`)); + .catch((e) => Logger.error`Failed to send error message: ${e}`); } }, }; diff --git a/packages/ai/src/tools/common/index.ts b/packages/ai/src/tools/common/index.ts index e54108be..80b31db7 100644 --- a/packages/ai/src/tools/common/index.ts +++ b/packages/ai/src/tools/common/index.ts @@ -2,6 +2,7 @@ import { Schema, tool } from 'ai'; import { AiContext } from '../../context'; import { getAiWorkerContext } from '../../ai-context-worker'; import { z } from 'zod'; +import { z as z3 } from 'zod/v3'; /** * Utility type that represents a value that can be either synchronous or asynchronous. @@ -14,7 +15,7 @@ type Awaitable = T | Promise; * Type representing the parameters schema for AI tools. * Extracted from the first parameter of the `tool` function from the 'ai' library. */ -export type ToolParameterType = z.ZodType | Schema; +export type ToolParameterType = z.ZodType | z3.ZodType | Schema; /** * Utility type that infers the TypeScript type from a tool parameter schema. @@ -26,8 +27,9 @@ export type InferParameters = ? T['_type'] : T extends z.ZodTypeAny ? z.infer - : never; - + : T extends z3.ZodTypeAny + ? z3.infer + : never; /** * Configuration options for creating an AI tool. * @template T - The parameter schema type for the tool diff --git a/packages/cache/src/use-cache-directive.ts b/packages/cache/src/use-cache-directive.ts index c0910148..328795ca 100644 --- a/packages/cache/src/use-cache-directive.ts +++ b/packages/cache/src/use-cache-directive.ts @@ -2,7 +2,6 @@ import { CommonDirectiveTransformer, CommonDirectiveTransformerOptions, CompilerPluginRuntime, - Logger, } from 'commandkit'; /** @@ -26,6 +25,5 @@ export class UseCacheDirectivePlugin extends CommonDirectiveTransformer { public async activate(ctx: CompilerPluginRuntime): Promise { super.activate(ctx); - Logger.info('"use cache" directive compiler plugin activated'); } } diff --git a/packages/commandkit/package.json b/packages/commandkit/package.json index c113f02e..fb06a395 100644 --- a/packages/commandkit/package.json +++ b/packages/commandkit/package.json @@ -54,75 +54,85 @@ "import": "./dist/index.js", "types": "./dist/index.d.ts" }, - "./jsx-runtime": { - "require": "./jsx-runtime.cjs", - "import": "./jsx-runtime.cjs", - "types": "./jsx-runtime.d.ts" + "./ai": { + "require": "./ai.cjs", + "import": "./ai.cjs", + "types": "./ai.d.ts" }, - "./hooks": { - "require": "./hooks.cjs", - "import": "./hooks.cjs", - "types": "./hooks.d.ts" + "./analytics": { + "require": "./analytics.cjs", + "import": "./analytics.cjs", + "types": "./analytics.d.ts" }, - "./plugin": { - "require": "./plugin.cjs", - "import": "./plugin.cjs", - "types": "./plugin.d.ts" + "./async-queue": { + "require": "./async-queue.cjs", + "import": "./async-queue.cjs", + "types": "./async-queue.d.ts" }, - "./config": { - "require": "./config.cjs", - "import": "./config.cjs", - "types": "./config.d.ts" + "./cache": { + "require": "./cache.cjs", + "import": "./cache.cjs", + "types": "./cache.d.ts" }, "./components": { "require": "./components.cjs", "import": "./components.cjs", "types": "./components.d.ts" }, + "./config": { + "require": "./config.cjs", + "import": "./config.cjs", + "types": "./config.d.ts" + }, + "./env": { + "require": "./env.cjs", + "import": "./env.cjs", + "types": "./env.d.ts" + }, + "./events": { + "require": "./events.cjs", + "import": "./events.cjs", + "types": "./events.d.ts" + }, "./flag": { "require": "./flag.cjs", "import": "./flag.cjs", "types": "./flag.d.ts" }, - "./cache": { - "require": "./cache.cjs", - "import": "./cache.cjs", - "types": "./cache.d.ts" + "./hooks": { + "require": "./hooks.cjs", + "import": "./hooks.cjs", + "types": "./hooks.d.ts" }, "./i18n": { "require": "./i18n.cjs", "import": "./i18n.cjs", "types": "./i18n.d.ts" }, + "./jsx-runtime": { + "require": "./jsx-runtime.cjs", + "import": "./jsx-runtime.cjs", + "types": "./jsx-runtime.d.ts" + }, + "./kv": { + "require": "./kv.cjs", + "import": "./kv.cjs", + "types": "./kv.d.ts" + }, "./logger": { "require": "./logger.cjs", "import": "./logger.cjs", "types": "./logger.d.ts" }, - "./events": { - "require": "./events.cjs", - "import": "./events.cjs", - "types": "./events.d.ts" - }, - "./analytics": { - "require": "./analytics.cjs", - "import": "./analytics.cjs", - "types": "./analytics.d.ts" - }, - "./ai": { - "require": "./ai.cjs", - "import": "./ai.cjs", - "types": "./ai.d.ts" - }, - "./env": { - "require": "./env.cjs", - "import": "./env.cjs", - "types": "./env.d.ts" + "./mutex": { + "require": "./mutex.cjs", + "import": "./mutex.cjs", + "types": "./mutex.d.ts" }, - "./async-queue": { - "require": "./async-queue.cjs", - "import": "./async-queue.cjs", - "types": "./async-queue.d.ts" + "./plugin": { + "require": "./plugin.cjs", + "import": "./plugin.cjs", + "types": "./plugin.d.ts" }, "./ratelimit": { "require": "./ratelimit.cjs", @@ -133,21 +143,10 @@ "require": "./semaphore.cjs", "import": "./semaphore.cjs", "types": "./semaphore.d.ts" - }, - "./mutex": { - "require": "./mutex.cjs", - "import": "./mutex.cjs", - "types": "./mutex.d.ts" - }, - "./kv": { - "require": "./kv.cjs", - "import": "./kv.cjs", - "types": "./kv.d.ts" } }, "scripts": { "check-types": "tsc --noEmit", - "dev": "pnpm run --filter=test-bot dev", "build": "tsdown", "test": "vitest" }, @@ -160,7 +159,9 @@ "keywords": [ "discord.js", "command handler", - "event handler" + "event handler", + "framework", + "discord.js 14" ], "dependencies": { "@rollup/plugin-json": "^6.1.0", @@ -173,7 +174,7 @@ "picocolors": "^1.1.1", "rfdc": "^1.3.1", "rimraf": "^6.0.0", - "tsdown": "^0.14.0", + "tsdown": "^0.14.2", "use-macro": "^1.1.0" }, "devDependencies": { diff --git a/packages/commandkit/src/app/commands/AppCommandRunner.ts b/packages/commandkit/src/app/commands/AppCommandRunner.ts index 784566f7..4490ae04 100644 --- a/packages/commandkit/src/app/commands/AppCommandRunner.ts +++ b/packages/commandkit/src/app/commands/AppCommandRunner.ts @@ -1,22 +1,22 @@ import { ChatInputCommandInteraction, Interaction, Message } from 'discord.js'; +import { AnalyticsEvents } from '../../analytics/constants'; +import { + makeContextAwareFunction, + provideContext, + useEnvironment, +} from '../../context/async-context'; import { CommandKitEnvironment, CommandKitEnvironmentType, } from '../../context/environment'; import { Logger } from '../../logger/Logger'; +import { CommandKitErrorCodes, isErrorType } from '../../utils/error-codes'; import { AppCommandHandler, PreparedAppCommandExecution, RunCommand, } from '../handlers/AppCommandHandler'; import { CommandExecutionMode, MiddlewareContext } from './Context'; -import { - makeContextAwareFunction, - provideContext, - useEnvironment, -} from '../../context/async-context'; -import { CommandKitErrorCodes, isErrorType } from '../../utils/error-codes'; -import { AnalyticsEvents } from '../../analytics/constants'; /** * Options for running a command in CommandKit. @@ -53,6 +53,7 @@ export class AppCommandRunner { * Handles the complete command lifecycle including before/after middleware execution. * @param prepared - The prepared command execution data * @param source - The source interaction or message that triggered the command + * @param options - The options for running the command */ public async runCommand( prepared: PreparedAppCommandExecution, @@ -74,82 +75,111 @@ export class AppCommandRunner { env.variables.set('execHandlerKind', executionMode); env.variables.set('customHandler', options?.handler ?? null); - const ctx = new MiddlewareContext(commandkit, { - command: prepared.command, - environment: env, - executionMode, - interaction: !(source instanceof Message) - ? (source as ChatInputCommandInteraction) - : (null as never), - message: source instanceof Message ? source : (null as never), - forwarded: false, - customArgs: { - setCommandRunner: (fn: RunCommand) => { - runCommand = fn; + try { + const middlewareCtx = new MiddlewareContext(commandkit, { + command: prepared.command, + environment: env, + executionMode, + interaction: !(source instanceof Message) + ? (source as ChatInputCommandInteraction) + : (null as never), + message: source instanceof Message ? source : (null as never), + forwarded: false, + customArgs: { + setCommandRunner: (fn: RunCommand) => { + runCommand = fn; + }, }, - }, - messageCommandParser: prepared.messageCommandParser, - }); + messageCommandParser: prepared.messageCommandParser, + }); - let middlewaresCanceled = false; + const beforeMiddlewares = prepared.middlewares.filter( + (m) => m.data.beforeExecute, + ); + + let beforeMiddlewaresStopped = false; + + // Run middleware before command execution + if (beforeMiddlewares.length) { + await provideContext(env, async () => { + for (const middleware of beforeMiddlewares) { + try { + await middleware.data.beforeExecute(middlewareCtx); + } catch (e) { + if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) { + beforeMiddlewaresStopped = true; + Logger.debug( + `Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called inside a beforeExecute function at "${middleware.middleware.relativePath}"`, + ); + break; // Stop the middleware loop if `stopMiddlewares()` is called. + } + + if ( + isErrorType(e, [ + CommandKitErrorCodes.ForwardedCommand, + CommandKitErrorCodes.InvalidCommandPrefix, + ]) + ) { + continue; + } - // Run middleware before command execution - if (prepared.middlewares.length) { - await provideContext(env, async () => { - for (const middleware of prepared.middlewares) { - if (!middleware.data.beforeExecute) continue; - try { - await middleware.data.beforeExecute(ctx); - } catch (e) { - if (isErrorType(e, CommandKitErrorCodes.ExitMiddleware)) { - middlewaresCanceled = true; - return; + throw e; } + } + }); + } - if ( - isErrorType(e, [ - CommandKitErrorCodes.ForwardedCommand, - CommandKitErrorCodes.InvalidCommandPrefix, - ]) - ) { - continue; - } + let result: any; - throw e; - } - } - }); - } + let stopMiddlewaresCalledInCmd = false; - let result: any; + // If no `stopMiddlewares()` was called in a `beforeExecute` middleware, try to run the command + if (!beforeMiddlewaresStopped) { + const targetData = prepared.command.data; + const fn = targetData[options?.handler || executionMode]; - if (!ctx.cancelled) { - // Determine which function to run based on whether we're executing a command or subcommand - const targetData = prepared.command.data; - const fn = targetData[options?.handler || executionMode]; + if (!fn) { + Logger.warn( + `Command ${prepared.command.command.name} has no handler for ${executionMode}`, + ); + } - if (!fn) { - Logger.warn( - `Command ${prepared.command.command.name} has no handler for ${executionMode}`, - ); - } + const analytics = commandkit.analytics; - const analytics = commandkit.analytics; - - if (fn) { - try { - const _executeCommand = makeContextAwareFunction( - env, - async () => { - env.registerDeferredFunction(async (env) => { - env.markEnd(); - const error = env.getExecutionError(); - const marker = env.getMarker(); - const time = `${env.getExecutionTime().toFixed(2)}ms`; - - if (error) { - Logger.error( - `[${marker} - ${time}] Error executing command: ${error.stack || error}`, + if (fn) { + try { + const _executeCommand = makeContextAwareFunction( + env, + async () => { + env.registerDeferredFunction(async (env) => { + env.markEnd(); + const error = env.getExecutionError(); + const marker = env.getMarker(); + const time = `${env.getExecutionTime().toFixed(2)}ms`; + + if (error) { + Logger.error`[${marker} - ${time}] Error executing command: ${error}`; + + const commandName = + prepared.command?.data?.command?.name ?? + prepared.command.command.name; + + await analytics.track({ + name: AnalyticsEvents.COMMAND_EXECUTION, + id: commandName, + data: { + error: true, + executionTime: env.getExecutionTime().toFixed(2), + type: executionMode, + command: commandName, + }, + }); + + return; + } + + Logger.info( + `[${marker} - ${time}] Command executed successfully`, ); const commandName = @@ -160,106 +190,95 @@ export class AppCommandRunner { name: AnalyticsEvents.COMMAND_EXECUTION, id: commandName, data: { - error: true, + error: false, executionTime: env.getExecutionTime().toFixed(2), type: executionMode, command: commandName, }, }); - - return; - } - - Logger.info( - `[${marker} - ${time}] Command executed successfully`, - ); - - const commandName = - prepared.command?.data?.command?.name ?? - prepared.command.command.name; - - await analytics.track({ - name: AnalyticsEvents.COMMAND_EXECUTION, - id: commandName, - data: { - error: false, - executionTime: env.getExecutionTime().toFixed(2), - type: executionMode, - command: commandName, - }, }); - }); - - return fn(ctx.clone()); - }, - this.#finalizer.bind(this), - ); - - const executeCommand = - runCommand != null - ? (runCommand as RunCommand)(_executeCommand) - : _executeCommand; - - env.markStart(prepared.command.data.command.name); - const res = await commandkit.plugins.execute(async (ctx, plugin) => { - return plugin.executeCommand( - ctx, - env, - source, - prepared, - executeCommand, + return fn(middlewareCtx.clone()); + }, + this.#finalizer.bind(this), ); - }); - if (!res) { - result = await executeCommand(); - } - } catch (e) { - if (isErrorType(e, CommandKitErrorCodes.ExitMiddleware)) { - middlewaresCanceled = true; - } + const executeCommand = + runCommand != null + ? (runCommand as RunCommand)(_executeCommand) + : _executeCommand; + + env.markStart(prepared.command.data.command.name); + + const res = await commandkit.plugins.execute( + async (ctx, plugin) => { + return plugin.executeCommand( + ctx, + env, + source, + prepared, + executeCommand, + ); + }, + ); - if ( - !isErrorType(e, [ - CommandKitErrorCodes.ForwardedCommand, - CommandKitErrorCodes.ExitMiddleware, - ]) - ) { - if (shouldThrowOnError) { - throw e; + if (!res) { + result = await executeCommand(); + } + } catch (e) { + if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) { + stopMiddlewaresCalledInCmd = true; + Logger.debug( + `Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called by the command itself`, + ); + } else if (!isErrorType(e, CommandKitErrorCodes.ForwardedCommand)) { + if (shouldThrowOnError) { + throw e; + } + Logger.error`${e}`; } - - Logger.error(e); } } + } else { + result = { + error: true, + message: + 'Command execution was cancelled by a beforeExecute middleware.', + }; } - } else { - result = { - error: true, - message: 'Command execution was cancelled by the middleware.', - }; - } - // Run middleware after command execution - if (!middlewaresCanceled && prepared.middlewares.length) { - await provideContext(env, async () => { - for (const middleware of prepared.middlewares) { - if (!middleware.data.afterExecute) continue; - try { - await middleware.data.afterExecute(ctx); - } catch (e) { - if (isErrorType(e, CommandKitErrorCodes.ExitMiddleware)) { - return; + const afterMiddlewares = prepared.middlewares.filter( + (m) => m.data.afterExecute, + ); + + // Run middleware after command execution only if `stopMiddlewares()` wasn't + // called in either `beforeExecute` middleware or in the command itself. + if ( + !beforeMiddlewaresStopped && + !stopMiddlewaresCalledInCmd && + afterMiddlewares.length + ) { + await provideContext(env, async () => { + for (const middleware of afterMiddlewares) { + try { + await middleware.data.afterExecute(middlewareCtx); + } catch (e) { + if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) { + Logger.debug( + `Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called inside an afterExecute function at "${middleware.middleware.relativePath}"`, + ); + break; // Stop the afterExecute middleware loop if `stopMiddlewares()` is called. + } + throw e; } - - throw e; } - } - }); - } + }); + } - return result; + return result; + } finally { + await this.#finalizer(env, false); + } } /** @@ -267,17 +286,19 @@ export class AppCommandRunner { * @internal * Finalizes command execution by running deferred functions and plugin cleanup. */ - async #finalizer() { - const env = useEnvironment(); + async #finalizer(env?: CommandKitEnvironment, runPlugins = true) { + env ??= useEnvironment(); await env.runDeferredFunctions(); env.clearAllDeferredFunctions(); // plugins may have their own deferred function, useful for cleanup or post-command analytics - await this.handler.commandkit.plugins.execute(async (ctx, plugin) => { - await plugin.onAfterCommand(ctx, env); - }); + if (runPlugins) { + await this.handler.commandkit.plugins.execute(async (ctx, plugin) => { + await plugin.onAfterCommand(ctx, env); + }); + } } /** diff --git a/packages/commandkit/src/app/commands/Context.ts b/packages/commandkit/src/app/commands/Context.ts index 6d75744a..df5b1be0 100644 --- a/packages/commandkit/src/app/commands/Context.ts +++ b/packages/commandkit/src/app/commands/Context.ts @@ -1,29 +1,29 @@ import { AutocompleteInteraction, + Awaitable, ChatInputCommandInteraction, - MessageContextMenuCommandInteraction, - Message, - Locale, - Interaction, - UserContextMenuCommandInteraction, Client, - Awaitable, Guild, + Interaction, + Locale, + Message, + MessageContextMenuCommandInteraction, TextBasedChannel, + UserContextMenuCommandInteraction, } from 'discord.js'; import { CommandKit } from '../../commandkit'; -import { - MessageCommandOptions, - MessageCommandParser, -} from './MessageCommandParser'; -import { CommandKitEnvironment } from '../../context/environment'; import { GenericFunction, getContext } from '../../context/async-context'; -import { exitMiddleware, redirect } from '../interrupt/signals'; +import { CommandKitEnvironment } from '../../context/environment'; import { LoadedCommand, ResolvableCommand, RunCommand, } from '../handlers/AppCommandHandler'; +import { redirect } from '../interrupt/signals'; +import { + MessageCommandOptions, + MessageCommandParser, +} from './MessageCommandParser'; /** * Enumeration of different command execution modes supported by CommandKit. @@ -551,15 +551,15 @@ export class Context< return []; } - /** - * Stops upcoming middleware or current command execution. - * If this is called inside pre-stage middleware, the next run will be the actual command, skipping all other pre-stage middlewares. - * If this is called inside a command itself, it will skip all post-stage middlewares. - * If this is called inside post-stage middleware, it will skip all other post-stage middlewares. - */ - public exit(): never { - exitMiddleware(); - } + // /** + // * Stops upcoming middleware or current command execution. + // * If this is called inside pre-stage middleware, the next run will be the actual command, skipping all other pre-stage middlewares. + // * If this is called inside a command itself, it will skip all post-stage middlewares. + // * If this is called inside post-stage middleware, it will skip all other post-stage middlewares. + // */ + // public exit(): never { + // stopMiddlewares(); + // } } /** @@ -568,26 +568,6 @@ export class Context< export class MiddlewareContext< T extends CommandExecutionMode = CommandExecutionMode, > extends Context { - /** - * @private - * @internal - */ - #cancel = false; - - /** - * Whether the command execution was cancelled. - */ - public get cancelled(): boolean { - return this.#cancel; - } - - /** - * Cancels the command execution. - */ - public cancel(): void { - this.#cancel = true; - } - /** * Sets command runner function to wrap the command execution. * @param fn The function to set. diff --git a/packages/commandkit/src/app/handlers/AppCommandHandler.ts b/packages/commandkit/src/app/handlers/AppCommandHandler.ts index fed22235..bc63d713 100644 --- a/packages/commandkit/src/app/handlers/AppCommandHandler.ts +++ b/packages/commandkit/src/app/handlers/AppCommandHandler.ts @@ -462,9 +462,7 @@ export class AppCommandHandler { COMMANDKIT_IS_DEV && this.commandkit.config.showUnknownPrefixCommandsWarning ) { - Logger.error( - `Prefix command "${command}" was not found.\nNote: This warning is only shown in development mode as an alert to help you find the command. If you wish to remove this warning, set \`showUnknownPrefixCommandsWarning\` to \`false\` in your commandkit config.`, - ); + Logger.error`Prefix command "${command}" was not found.\nNote: This warning is only shown in development mode as an alert to help you find the command. If you wish to remove this warning, set \`showUnknownPrefixCommandsWarning\` to \`false\` in your commandkit config.`; } return null; } @@ -502,7 +500,7 @@ export class AppCommandHandler { if (isErrorType(e, CommandKitErrorCodes.InvalidCommandPrefix)) { return null; } - Logger.error(e); + Logger.error`${e}`; return null; } } else { diff --git a/packages/commandkit/src/app/handlers/AppEventsHandler.ts b/packages/commandkit/src/app/handlers/AppEventsHandler.ts index cc35a7d2..0f42fddd 100644 --- a/packages/commandkit/src/app/handlers/AppEventsHandler.ts +++ b/packages/commandkit/src/app/handlers/AppEventsHandler.ts @@ -105,9 +105,7 @@ export class AppEventsHandler { const handler = await import(toFileURL(listener, true)); if (!handler.default || typeof handler.default !== 'function') { - Logger.error( - `Event handler for ${event.event}${event.namespace ? ` of namespace ${event.namespace}` : ''} does not have a default export or is not a function`, - ); + Logger.error`Event handler for ${event.event}${event.namespace ? ` of namespace ${event.namespace}` : ''} does not have a default export or is not a function`; } listeners.push({ diff --git a/packages/commandkit/src/app/interrupt/signals.ts b/packages/commandkit/src/app/interrupt/signals.ts index f12200ab..eba195c5 100644 --- a/packages/commandkit/src/app/interrupt/signals.ts +++ b/packages/commandkit/src/app/interrupt/signals.ts @@ -6,13 +6,10 @@ import { import { eventWorkerContext } from '../events/EventWorkerContext'; /** - * Cancel upcoming middleware execution. - * If this is called inside pre-stage middleware, the next run will be the actual command, skipping all other pre-stage middlewares. - * If this is called inside a command itself, it will skip all post-stage middlewares. - * If this is called inside post-stage middleware, it will skip all other post-stage middlewares. + * Stop upcoming middlewares and command execution. */ -export function exitMiddleware(): never { - throw createCommandKitError(CommandKitErrorCodes.ExitMiddleware); +export function stopMiddlewares(): never { + throw createCommandKitError(CommandKitErrorCodes.StopMiddlewares); } /** diff --git a/packages/commandkit/src/app/middlewares/permissions.ts b/packages/commandkit/src/app/middlewares/permissions.ts index 99e768e5..e7aac3f6 100644 --- a/packages/commandkit/src/app/middlewares/permissions.ts +++ b/packages/commandkit/src/app/middlewares/permissions.ts @@ -2,6 +2,7 @@ import { EmbedBuilder, MessageFlags } from 'discord.js'; import { getConfig } from '../../config/config'; import { Logger } from '../../logger/Logger'; import { MiddlewareContext } from '../commands/Context'; +import { stopMiddlewares } from '../interrupt/signals'; export const middlewareId = crypto.randomUUID(); @@ -46,7 +47,7 @@ export async function beforeExecute(ctx: MiddlewareContext) { Logger.error`Could not send 'Server-only command' DM to user ${interaction?.user.id ?? message?.author.id} for command ${command.command.name}: ${error}`; } - return ctx.cancel(); // Stop the command from executing + stopMiddlewares(); // Stop the command from executing } const userPermissions = @@ -153,5 +154,5 @@ export async function beforeExecute(ctx: MiddlewareContext) { Logger.error`Could not send 'Not enough permissions' reply to user ${interaction?.user.id ?? message?.author.id} for command ${command.command.name}: ${error}`; } - return ctx.cancel(); // Stop the command from executing + stopMiddlewares(); // Stop the command from executing } diff --git a/packages/commandkit/src/cli/build.ts b/packages/commandkit/src/cli/build.ts index ac61b774..eeffad9f 100644 --- a/packages/commandkit/src/cli/build.ts +++ b/packages/commandkit/src/cli/build.ts @@ -1,16 +1,17 @@ -import { build, Options } from 'tsdown'; -import { CompilerPlugin, CompilerPluginRuntime } from '../plugins'; -import { loadConfigFile } from '../config/loader'; +import { existsSync } from 'node:fs'; import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { DevEnv, devEnvFileArgs, ProdEnv, prodEnvFileArgs } from './env'; import { rimraf } from 'rimraf'; -import { performTypeCheck } from './type-checker'; -import { copyLocaleFiles } from './common'; +import { build, Options } from 'tsdown'; + import { MaybeArray } from '../components'; -import { COMMANDKIT_CWD } from '../utils/constants'; +import { loadConfigFile } from '../config/loader'; import { mergeDeep } from '../config/utils'; -import { existsSync } from 'node:fs'; +import { CompilerPlugin, CompilerPluginRuntime } from '../plugins'; +import { COMMANDKIT_CWD } from '../utils/constants'; +import { copyLocaleFiles } from './common'; +import { devEnvFileArgs, prodEnvFileArgs } from './env'; +import { performTypeCheck } from './type-checker'; /** * @private diff --git a/packages/commandkit/src/cli/development.ts b/packages/commandkit/src/cli/development.ts index c273f658..1c27060e 100644 --- a/packages/commandkit/src/cli/development.ts +++ b/packages/commandkit/src/cli/development.ts @@ -164,7 +164,7 @@ export async function bootstrapDevelopmentServer(configPath?: string) { } return false; - }, 300); + }, 700); const isConfigUpdate = (path: string) => { const isConfig = configPaths.some((configPath) => path === configPath); diff --git a/packages/commandkit/src/commandkit.ts b/packages/commandkit/src/commandkit.ts index 1d4de3b5..a4a699ff 100644 --- a/packages/commandkit/src/commandkit.ts +++ b/packages/commandkit/src/commandkit.ts @@ -254,7 +254,7 @@ export class CommandKit extends EventEmitter { } catch (e) { // ignore if (process.env.COMMANDKIT_DEBUG_TYPEGEN) { - Logger.error(e); + Logger.error`${e}`; } } } diff --git a/packages/commandkit/src/context/async-context.ts b/packages/commandkit/src/context/async-context.ts index 3d8d113f..a7cc915a 100644 --- a/packages/commandkit/src/context/async-context.ts +++ b/packages/commandkit/src/context/async-context.ts @@ -70,6 +70,9 @@ export function makeContextAwareFunction< } catch (e) { if (!isCommandKitError(e)) { env.setExecutionError(e as Error); + } else { + // rethrow commandkit errors so they can be handled by the caller + throw e; } } finally { if (typeof finalizer === 'function') { diff --git a/packages/commandkit/src/flags/feature-flags.ts b/packages/commandkit/src/flags/feature-flags.ts index 3291fee5..a94f1f13 100644 --- a/packages/commandkit/src/flags/feature-flags.ts +++ b/packages/commandkit/src/flags/feature-flags.ts @@ -352,9 +352,7 @@ export class FeatureFlag { } } } catch (error) { - Logger.error( - `Error fetching flag provider configuration for "${this.options.key}": ${error}`, - ); + Logger.error`Error fetching flag provider configuration for "${this.options.key}": ${error}`; // continue with local decision if provider fails } } diff --git a/packages/commandkit/src/index.ts b/packages/commandkit/src/index.ts index b7165166..2a672f8d 100644 --- a/packages/commandkit/src/index.ts +++ b/packages/commandkit/src/index.ts @@ -6,7 +6,13 @@ export * from './commandkit'; export * from './components'; export * from './config/config'; export * from './context/async-context'; -export * from './context/environment'; +export { + type CommandKitEnvironmentInternalData, + CommandKitEnvironment, + CommandKitEnvironmentType, + cancelAfter as unstable_cancelAfter, + after as unstable_after, +} from './context/environment'; export * from './app/index'; export * from './logger/DefaultLogger'; export * from './logger/ILogger'; diff --git a/packages/commandkit/src/logger/DefaultLogger.ts b/packages/commandkit/src/logger/DefaultLogger.ts index c791573d..8ce74a74 100644 --- a/packages/commandkit/src/logger/DefaultLogger.ts +++ b/packages/commandkit/src/logger/DefaultLogger.ts @@ -139,15 +139,20 @@ export class DefaultLogger implements ILogger { const context = this._getContext(); const colorFn = TextColorMap[level]; + let processedMessage = message; + if (message instanceof Error) { + processedMessage = `${message.message}\n${message.stack}`; + } + if (context) { this.logger.log( `${prefix}\n${context} ${colors.dim(BoxChars.corner)}`, - colorFn(message), + colorFn(processedMessage), ); } else { this.logger.log( `${prefix} ${colors.dim(BoxChars.corner)}`, - colorFn(message), + colorFn(processedMessage), ); } } @@ -165,10 +170,14 @@ export class DefaultLogger implements ILogger { for (let i = 0; i < strings.length; i++) { result += strings[i]; if (i < values.length) { - result += inspect(values[i], { - colors: COMMANDKIT_IS_DEV, - depth: 2, - }); + const value = values[i]; + if (value instanceof Error) { + result += `${value.message}\n${value.stack}`; + } else if (value !== null && typeof value === 'object') { + result += inspect(value, { colors: true, depth: 2 }); + } else { + result += value; + } } } diff --git a/packages/commandkit/src/plugins/plugin-runtime/CompilerPluginRuntime.ts b/packages/commandkit/src/plugins/plugin-runtime/CompilerPluginRuntime.ts index a24a34af..b6a25b53 100644 --- a/packages/commandkit/src/plugins/plugin-runtime/CompilerPluginRuntime.ts +++ b/packages/commandkit/src/plugins/plugin-runtime/CompilerPluginRuntime.ts @@ -209,9 +209,7 @@ export class CompilerPluginRuntime { }, ); } catch (e: any) { - console.error( - `Plugin ${plugin.name} failed to activate with ${e?.stack || e}`, - ); + Logger.error`Plugin ${plugin.name} failed to activate with ${e}`; } } @@ -237,9 +235,7 @@ export class CompilerPluginRuntime { }, ); } catch (e: any) { - console.error( - `Plugin ${plugin.name} failed to deactivate with ${e?.stack || e}`, - ); + Logger.error`Plugin ${plugin.name} failed to deactivate with ${e}`; } } diff --git a/packages/commandkit/src/utils/error-codes.ts b/packages/commandkit/src/utils/error-codes.ts index 1c823273..a3ffcaff 100644 --- a/packages/commandkit/src/utils/error-codes.ts +++ b/packages/commandkit/src/utils/error-codes.ts @@ -5,7 +5,7 @@ export const CommandKitErrorCodes = { /** * Error code for exiting middleware. */ - ExitMiddleware: Symbol('kExitMiddleware'), + StopMiddlewares: Symbol('kStopMiddlewares'), /** * Error code for forwarded commands. */ diff --git a/packages/create-commandkit/templates/JavaScript/src/app/events/clientReady/log.js b/packages/create-commandkit/templates/JavaScript/src/app/events/clientReady/log.js index 44f2cf81..abb2fe51 100644 --- a/packages/create-commandkit/templates/JavaScript/src/app/events/clientReady/log.js +++ b/packages/create-commandkit/templates/JavaScript/src/app/events/clientReady/log.js @@ -1,7 +1,7 @@ import { Logger } from 'commandkit/logger'; /** - * @type {import('commandkit').EventHandler<'ready'>} + * @type {import('commandkit').EventHandler<'clientReady'>} */ const handler = async (client) => { Logger.info(`Logged in as ${client.user.username}!`); diff --git a/packages/create-commandkit/templates/TypeScript/src/app/events/clientReady/log.ts b/packages/create-commandkit/templates/TypeScript/src/app/events/clientReady/log.ts index cde182ab..e1ff76c0 100644 --- a/packages/create-commandkit/templates/TypeScript/src/app/events/clientReady/log.ts +++ b/packages/create-commandkit/templates/TypeScript/src/app/events/clientReady/log.ts @@ -1,7 +1,7 @@ import type { EventHandler } from 'commandkit'; import { Logger } from 'commandkit/logger'; -const handler: EventHandler<'ready'> = async (client) => { +const handler: EventHandler<'clientReady'> = async (client) => { Logger.info(`Logged in as ${client.user.username}!`); }; diff --git a/packages/legacy/src/plugin.ts b/packages/legacy/src/plugin.ts index 1ec13e00..b92ecdbb 100644 --- a/packages/legacy/src/plugin.ts +++ b/packages/legacy/src/plugin.ts @@ -13,6 +13,7 @@ import { HMREventType, getSourceDirectories, CommandKitEventDispatch, + stopMiddlewares, } from 'commandkit'; import { join, resolve } from 'node:path'; import { loadLegacyValidations } from './loadLegacyValidations.js'; @@ -254,7 +255,7 @@ export class LegacyHandlerPlugin extends RuntimePlugin