Skip to content

Commit 13cffbd

Browse files
committed
refactor: allow optional description, fix loaded commands guild prop + improve type safety
1 parent e08f987 commit 13cffbd

File tree

3 files changed

+52
-26
lines changed

3 files changed

+52
-26
lines changed

apps/test-bot/src/app/commands/(general)/(animal)/cat.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {
2-
CommandData,
32
ChatInputCommand,
3+
CommandData,
44
MessageCommand,
55
MessageContextMenuCommand,
66
} from 'commandkit';
@@ -12,7 +12,7 @@ import {
1212

1313
export const command: CommandData = {
1414
name: 'cat',
15-
description: 'cat command',
15+
// description: 'cat command',
1616
integration_types: [
1717
ApplicationIntegrationType.GuildInstall,
1818
ApplicationIntegrationType.UserInstall,

packages/commandkit/src/app/handlers/AppCommandHandler.ts

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { CommandKit } from '../../CommandKit';
21
import {
32
AutocompleteInteraction,
43
Awaitable,
@@ -10,18 +9,20 @@ import {
109
Message,
1110
SlashCommandBuilder,
1211
} from 'discord.js';
13-
import { Context } from '../commands/Context';
14-
import { toFileURL } from '../../utils/resolve-file-url';
15-
import { MessageCommandParser } from '../commands/MessageCommandParser';
16-
import { CommandKitErrorCodes, isErrorType } from '../../utils/error-codes';
17-
import { CommandRegistrar } from '../register/CommandRegistrar';
12+
import type { CommandKit } from '../../CommandKit';
1813
import { AsyncFunction, GenericFunction } from '../../context/async-context';
1914
import { Logger } from '../../logger/Logger';
20-
import { Command, Middleware } from '../router';
21-
import { AppCommandRunner } from '../commands/AppCommandRunner';
15+
import type { CommandData } from '../../types';
16+
import colors from '../../utils/colors';
2217
import { COMMANDKIT_IS_DEV } from '../../utils/constants';
18+
import { CommandKitErrorCodes, isErrorType } from '../../utils/error-codes';
19+
import { toFileURL } from '../../utils/resolve-file-url';
2320
import { rewriteCommandDeclaration } from '../../utils/types-package';
24-
import colors from '../../utils/colors';
21+
import { AppCommandRunner } from '../commands/AppCommandRunner';
22+
import { Context } from '../commands/Context';
23+
import { MessageCommandParser } from '../commands/MessageCommandParser';
24+
import { CommandRegistrar } from '../register/CommandRegistrar';
25+
import { Command, Middleware } from '../router';
2526

2627
/**
2728
* Function type for wrapping command execution with custom logic.
@@ -34,7 +35,7 @@ export type RunCommand = <T extends AsyncFunction>(fn: T) => T;
3435
* It can be used to define slash commands, context menu commands, and message commands.
3536
*/
3637
export interface AppCommandNative {
37-
command: SlashCommandBuilder | Record<string, any>;
38+
command: CommandData | Record<string, any>;
3839
chatInput?: (ctx: Context) => Awaitable<unknown>;
3940
autocomplete?: (ctx: Context) => Awaitable<unknown>;
4041
message?: (ctx: Context) => Awaitable<unknown>;
@@ -127,6 +128,10 @@ const commandDataSchema = {
127128
userContextMenu: (c: unknown) => typeof c === 'function',
128129
};
129130

131+
export type CommandDataSchema = typeof commandDataSchema;
132+
export type CommandDataSchemaKey = keyof CommandDataSchema;
133+
export type CommandDataSchemaValue = CommandDataSchema[CommandDataSchemaKey];
134+
130135
/**
131136
* @private
132137
* @internal
@@ -682,21 +687,35 @@ export class AppCommandHandler {
682687
return;
683688
}
684689

685-
const data = await import(`${toFileURL(command.path)}?t=${Date.now()}`);
690+
const commandFileData = (await import(
691+
`${toFileURL(command.path)}?t=${Date.now()}`
692+
)) as AppCommandNative;
686693

687-
if (!data.command) {
694+
if (!commandFileData.command) {
688695
throw new Error(
689696
`Invalid export for command ${command.name}: no command definition found`,
690697
);
691698
}
692699

693700
let handlerCount = 0;
694-
for (const [key, validator] of Object.entries(commandDataSchema)) {
695-
if (key !== 'command' && data[key]) handlerCount++;
696-
if (data[key] && !(await validator(data[key]))) {
697-
throw new Error(
698-
`Invalid export for command ${command.name}: ${key} does not match expected value`,
699-
);
701+
702+
for (const [key, propValidator] of Object.entries(commandDataSchema) as [
703+
CommandDataSchemaKey,
704+
CommandDataSchemaValue,
705+
][]) {
706+
const exportedProp = commandFileData[key];
707+
708+
if (exportedProp) {
709+
if (!(await propValidator(exportedProp))) {
710+
throw new Error(
711+
`Invalid export for command ${command.name}: ${key} does not match expected value`,
712+
);
713+
}
714+
715+
if (key !== 'command') {
716+
// command file includes a handler function (chatInput, message, etc)
717+
handlerCount++;
718+
}
700719
}
701720
}
702721

@@ -706,7 +725,7 @@ export class AppCommandHandler {
706725
);
707726
}
708727

709-
let lastUpdated = data.command;
728+
let lastUpdated = commandFileData.command;
710729

711730
await this.commandkit.plugins.execute(async (ctx, plugin) => {
712731
const res = await plugin.prepareCommand(ctx, lastUpdated);
@@ -718,9 +737,9 @@ export class AppCommandHandler {
718737

719738
this.loadedCommands.set(id, {
720739
command,
721-
guilds: data.guilds,
740+
guilds: commandFileData.command.guilds,
722741
data: {
723-
...data,
742+
...commandFileData,
724743
command: 'toJSON' in lastUpdated ? lastUpdated.toJSON() : lastUpdated,
725744
},
726745
});

packages/commandkit/src/types.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {
66
} from 'discord.js';
77
import type { CommandKit } from './CommandKit';
88

9+
type Prettify<T> = {
10+
[K in keyof T]: T[K];
11+
} & {};
12+
913
/**
1014
* Options for instantiating a CommandKit handler.
1115
*/
@@ -40,6 +44,9 @@ export interface CommandContext<
4044
/**
4145
* Represents a command that can be executed by CommandKit.
4246
*/
43-
export type CommandData = RESTPostAPIApplicationCommandsJSONBody & {
44-
guilds?: string[];
45-
};
47+
export type CommandData = Prettify<
48+
Omit<RESTPostAPIApplicationCommandsJSONBody, 'description'> & {
49+
description?: string;
50+
guilds?: string[];
51+
}
52+
>;

0 commit comments

Comments
 (0)