Skip to content

Commit cdd4c5a

Browse files
committed
feat: allow importing commandkit directly
1 parent 885a05c commit cdd4c5a

File tree

5 files changed

+129
-47
lines changed

5 files changed

+129
-47
lines changed

apps/test-bot/src/app.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Client } from 'discord.js';
2-
import { Logger, onApplicationBootstrap } from 'commandkit';
2+
import { Logger, commandkit } from 'commandkit';
33

44
const client = new Client({
55
intents: [
@@ -11,16 +11,15 @@ const client = new Client({
1111
],
1212
});
1313

14-
onApplicationBootstrap((commandkit) => {
15-
Logger.log('Application bootstrapped successfully!');
16-
commandkit.setPrefixResolver((message) => {
17-
return [
18-
`<@${message.client.user.id}>`,
19-
`<@!${message.client.user.id}>`,
20-
'!',
21-
'?',
22-
];
23-
});
14+
Logger.log('Application bootstrapped successfully!');
15+
16+
commandkit.setPrefixResolver((message) => {
17+
return [
18+
`<@${message.client.user.id}>`,
19+
`<@!${message.client.user.id}>`,
20+
'!',
21+
'?',
22+
];
2423
});
2524

2625
export default client;

packages/commandkit/src/CommandKit.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { CommandKitOptions } from './types';
33
import colors from './utils/colors';
44
import { createElement, Fragment } from './components';
55
import { EventInterceptor } from './components/common/EventInterceptor';
6-
import { Awaitable, Events, Locale, Message } from 'discord.js';
7-
import { findAppDirectory } from './utils/utilities';
6+
import { Awaitable, Client, Events, Locale, Message } from 'discord.js';
7+
import { createProxy, findAppDirectory, SimpleProxy } from './utils/utilities';
88
import { join } from 'node:path';
99
import { AppCommandHandler } from './app/handlers/AppCommandHandler';
1010
import { CommandsRouter, EventsRouter } from './app/router';
@@ -26,9 +26,6 @@ export interface CommandKitConfiguration {
2626
getMessageCommandPrefix: (message: Message) => Awaitable<string | string[]>;
2727
}
2828

29-
// @ts-ignore
30-
export let commandkit: CommandKit;
31-
3229
export type BootstrapFunction =
3330
| GenericFunction<[CommandKit]>
3431
| AsyncFunction<[CommandKit]>;
@@ -74,7 +71,8 @@ export function onApplicationBootstrap<F extends BootstrapFunction>(
7471

7572
export class CommandKit extends EventEmitter {
7673
#started = false;
77-
private options: CommandKitOptions;
74+
#clientProxy: SimpleProxy<Client> | null = createProxy({} as Client);
75+
#client!: Client;
7876
public eventInterceptor!: EventInterceptor;
7977

8078
public static readonly createElement = createElement;
@@ -103,7 +101,7 @@ export class CommandKit extends EventEmitter {
103101
* @param options - The default CommandKit configuration.
104102
* @see {@link https://commandkit.js.org/docs/guide/commandkit-setup}
105103
*/
106-
constructor(options: CommandKitOptions) {
104+
constructor(options: CommandKitOptions = {}) {
107105
if (CommandKit.instance) {
108106
process.emitWarning(
109107
'CommandKit instance already exists. Having multiple instance in same project is discouraged and it may lead to unexpected behavior.',
@@ -113,24 +111,17 @@ export class CommandKit extends EventEmitter {
113111
);
114112
}
115113

116-
if (!options.client) {
117-
throw new Error(
118-
colors.red('"client" is required when instantiating CommandKit.'),
119-
);
120-
}
121-
122114
super();
123115

124-
this.options = options;
125-
126116
if (!CommandKit.instance) {
127117
CommandKit.instance = this;
128118
}
129119

130-
this.plugins = new CommandKitPluginRuntime(this);
120+
if (options?.client) {
121+
this.setClient(options.client);
122+
}
131123

132-
// @ts-ignore
133-
commandkit = CommandKit.instance;
124+
this.plugins = new CommandKitPluginRuntime(this);
134125

135126
this.#bootstrapHooks();
136127
}
@@ -171,7 +162,7 @@ export class CommandKit extends EventEmitter {
171162
async start(token?: string | false) {
172163
if (this.#started) return;
173164

174-
if (!this.options.client) {
165+
if (!this.#client) {
175166
throw new Error(
176167
colors.red('"client" is required when starting CommandKit.'),
177168
);
@@ -199,7 +190,7 @@ export class CommandKit extends EventEmitter {
199190
this.commandHandler.registerCommandHandler();
200191
this.incrementClientListenersCount();
201192

202-
if (token !== false && !this.options.client.isReady()) {
193+
if (token !== false && !this.client.isReady()) {
203194
this.client.once(Events.ClientReady, async () => {
204195
await this.commandHandler.registrar.register();
205196
});
@@ -210,16 +201,16 @@ export class CommandKit extends EventEmitter {
210201

211202
const botToken =
212203
token ??
213-
this.options.client.token ??
204+
this.client.token ??
214205
process.env.TOKEN ??
215206
process.env.DISCORD_TOKEN;
216207

217-
await this.options.client.login(botToken);
208+
await this.client.login(botToken);
218209

219210
await this.plugins.execute((ctx, plugin) => {
220211
return plugin.onAfterClientLogin?.(ctx);
221212
});
222-
} else if (this.options.client.isReady()) {
213+
} else if (this.client.isReady()) {
223214
await this.commandHandler.registrar.register();
224215
}
225216

@@ -272,8 +263,33 @@ export class CommandKit extends EventEmitter {
272263
/**
273264
* Get the client attached to this CommandKit instance.
274265
*/
275-
get client() {
276-
return this.options.client;
266+
get client(): Client {
267+
const client = this.#client || this.#clientProxy?.proxy;
268+
269+
if (!client) {
270+
throw new Error('Client instance is not set');
271+
}
272+
273+
return client;
274+
}
275+
276+
/**
277+
* Sets the client attached to this CommandKit instance.
278+
* @param client The client to set.
279+
*/
280+
setClient(client: Client) {
281+
this.#client = client;
282+
283+
// update the proxy target if it exists
284+
if (this.#clientProxy) {
285+
// this is a hack to update the proxy target
286+
// because some of the dependencies of commandkit may
287+
// depend on the client instance
288+
this.#clientProxy.setTarget(client);
289+
this.#clientProxy = null;
290+
}
291+
292+
return this;
277293
}
278294

279295
async #init() {
@@ -350,18 +366,14 @@ export class CommandKit extends EventEmitter {
350366
* Increment the client listeners count.
351367
*/
352368
incrementClientListenersCount() {
353-
this.options.client.setMaxListeners(
354-
this.options.client.getMaxListeners() + 1,
355-
);
369+
this.client.setMaxListeners(this.client.getMaxListeners() + 1);
356370
}
357371

358372
/**
359373
* Decrement the client listeners count.
360374
*/
361375
decrementClientListenersCount() {
362-
this.options.client.setMaxListeners(
363-
this.options.client.getMaxListeners() - 1,
364-
);
376+
this.client.setMaxListeners(this.client.getMaxListeners() - 1);
365377
}
366378

367379
/**
@@ -388,3 +400,5 @@ export class CommandKit extends EventEmitter {
388400
}
389401
}
390402
}
403+
404+
export const commandkit = CommandKit.instance || new CommandKit();

packages/commandkit/src/cli/build.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,17 @@ async function injectEntryFile(
134134
const code = `/* Entrypoint File Generated By CommandKit */
135135
${isDev ? `\n\n// Injected for development\n${wrapInAsyncIIFE([envScript(isDev), antiCrashScript])}\n\n` : wrapInAsyncIIFE([envScript(isDev)])}
136136
137-
import { CommandKit } from 'commandkit';
137+
import { commandkit } from 'commandkit';
138+
import { Client } from 'discord.js';
138139
139140
async function bootstrap() {
140141
const app = await import('./app.js').then((m) => m.default ?? m);
141-
const commandkit = new CommandKit({
142-
client: app,
143-
});
142+
143+
if (!app || !(app instanceof Client)) {
144+
throw new Error('The app file must default export the discord.js client instance');
145+
}
146+
147+
commandkit.setClient(app);
144148
145149
await commandkit.start();
146150
}

packages/commandkit/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface CommandKitOptions {
1313
/**
1414
* The Discord.js client object to use with CommandKit.
1515
*/
16-
client: Client;
16+
client?: Client;
1717
}
1818

1919
/**

packages/commandkit/src/utils/utilities.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,68 @@ export function stopEvents(): never {
133133

134134
throw new StopEventPropagationError();
135135
}
136+
137+
export interface SimpleProxy<T extends object> {
138+
proxy: T;
139+
setTarget(newTarget: T): void;
140+
}
141+
142+
/**
143+
* Creates a simple proxy object that mirrors the target object.
144+
* @param target The target object to proxy.
145+
* @returns The proxied object.
146+
*/
147+
export function createProxy<T extends object>(target: T): SimpleProxy<T> {
148+
let _target = target;
149+
150+
const proxy = new Proxy(_target, {
151+
get(target, prop, receiver) {
152+
return Reflect.get(_target, prop, receiver);
153+
},
154+
set(target, prop, value, receiver) {
155+
return Reflect.set(_target, prop, value, receiver);
156+
},
157+
deleteProperty(target, prop) {
158+
return Reflect.deleteProperty(_target, prop);
159+
},
160+
has(target, prop) {
161+
return Reflect.has(_target, prop);
162+
},
163+
ownKeys(target) {
164+
return Reflect.ownKeys(_target);
165+
},
166+
getOwnPropertyDescriptor(target, prop) {
167+
return Reflect.getOwnPropertyDescriptor(_target, prop);
168+
},
169+
defineProperty(target, prop, attributes) {
170+
return Reflect.defineProperty(_target, prop, attributes);
171+
},
172+
getPrototypeOf(target) {
173+
return Reflect.getPrototypeOf(_target);
174+
},
175+
setPrototypeOf(target, proto) {
176+
return Reflect.setPrototypeOf(_target, proto);
177+
},
178+
isExtensible(target) {
179+
return Reflect.isExtensible(_target);
180+
},
181+
preventExtensions(target) {
182+
return Reflect.preventExtensions(_target);
183+
},
184+
apply(target, thisArg, args) {
185+
// @ts-ignore
186+
return Reflect.apply(_target, thisArg, args);
187+
},
188+
construct(target, args, newTarget) {
189+
// @ts-ignore
190+
return Reflect.construct(_target, args, newTarget);
191+
},
192+
});
193+
194+
return {
195+
proxy,
196+
setTarget(newTarget: T) {
197+
_target = newTarget;
198+
},
199+
};
200+
}

0 commit comments

Comments
 (0)