bakit 2.0.0-alpha.23 → 2.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,70 +1,61 @@
1
1
  import { config } from 'dotenv';
2
2
  import { program } from 'commander';
3
3
  import { fork } from 'child_process';
4
+ import path, { resolve } from 'path';
4
5
  import chokidar from 'chokidar';
5
- import path, { relative, sep } from 'path';
6
- import { pathToFileURL } from 'url';
6
+ import { RPC } from 'bakit';
7
7
 
8
8
  // src/cli/bin.ts
9
- function getTopLevelDirectory(path2, entryDir) {
10
- return relative(entryDir, path2).split(sep)[0] ?? null;
11
- }
12
9
  var DevProcessManager = class {
13
10
  constructor(options) {
14
11
  this.options = options;
15
12
  }
16
- child = null;
13
+ rpc = null;
17
14
  restartTimer = null;
18
15
  start() {
19
16
  console.log("Starting bakit in dev mode..."), this.startChild(), this.startWatcher();
20
17
  }
21
18
  startChild() {
22
- if (this.child) return;
23
- let entry = path.resolve(this.options.entry);
24
- this.child = fork(entry, {
19
+ if (this.rpc)
20
+ return;
21
+ let entry = path.resolve(this.options.entry), child = fork(entry, {
25
22
  execArgv: ["--import", "bakit/register"],
26
23
  stdio: "inherit",
27
24
  env: {
28
25
  ...process.env,
29
26
  NODE_ENV: "development"
30
27
  }
31
- }), this.child.on("exit", () => {
32
- this.child = null;
33
28
  });
29
+ this.rpc = new RPC(child), this.rpc.on("restart", (fileUpdated) => this.scheduleRestart(fileUpdated));
34
30
  }
35
31
  restartChild() {
36
- if (!this.child)
32
+ if (!this.rpc)
37
33
  return this.startChild();
38
- let old = this.child;
39
- old.once("exit", () => {
40
- this.child = null, this.startChild();
41
- }), old.kill("SIGTERM");
34
+ let child = this.rpc.transport;
35
+ child.once("exit", () => {
36
+ this.rpc = null, this.startChild();
37
+ }), child.kill("SIGTERM");
42
38
  }
43
39
  startWatcher() {
44
- let { rootDir } = this.options;
45
- chokidar.watch(rootDir, {
40
+ let { rootDir } = this.options, watcher = chokidar.watch(rootDir, {
46
41
  ignoreInitial: true,
47
42
  awaitWriteFinish: {
48
43
  stabilityThreshold: 200,
49
44
  pollInterval: 50
50
45
  }
51
- }).on("change", (path2) => {
52
- this.onFileChanged(path2);
53
46
  });
47
+ watcher.on("change", (path2) => this.onFileUpdate("fileChange", path2)), watcher.on("unlink", (path2) => this.onFileUpdate("fileRemove", path2));
54
48
  }
55
- onFileChanged(path2) {
56
- if (!this.child)
57
- return;
58
- let top = getTopLevelDirectory(path2, this.options.rootDir);
59
- if (top && this.options.hotDirs.includes(top)) {
60
- this.child.connected && this.child.send({ type: `hmr:${top}`, url: pathToFileURL(path2).href });
61
- return;
49
+ onFileUpdate(type, path2) {
50
+ try {
51
+ this.rpc?.send(type, resolve(path2));
52
+ } catch {
53
+ this.scheduleRestart(true);
62
54
  }
63
- this.scheduleRestart();
64
55
  }
65
- scheduleRestart() {
56
+ scheduleRestart(fileUpdated = false) {
66
57
  this.restartTimer && clearTimeout(this.restartTimer), this.restartTimer = setTimeout(() => {
67
- console.log("Detected changes, restarting..."), this.restartChild(), this.restartTimer = null;
58
+ fileUpdated && console.log("File changes detected, restarting..."), this.restartChild(), this.restartTimer = null;
68
59
  }, 150);
69
60
  }
70
61
  };
package/dist/hooks.js CHANGED
@@ -3,11 +3,15 @@ import { readFile } from 'fs/promises';
3
3
  import { Module } from 'module';
4
4
  import { dirname, resolve as resolve$1, basename } from 'path';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
+ import { RPC } from 'bakit';
6
7
 
7
8
  // src/lib/loader/hooks.ts
8
- var EXTENSIONS = [".js", ".ts"], inDev = false, parentPort, versions, esbuild;
9
+ var EXTENSIONS = [".js", ".ts"], rpc, versions, esbuild;
10
+ function isDevelopment() {
11
+ return process.env.NODE_ENV === "development";
12
+ }
9
13
  async function initialize({ port }) {
10
- inDev = process.env.NODE_ENV === "development", inDev && (parentPort = port, parentPort.on("message", onMessage), versions = /* @__PURE__ */ new Map(), esbuild = await import('esbuild'));
14
+ rpc = new RPC(port), versions = /* @__PURE__ */ new Map(), rpc.on("message", onUnload), isDevelopment() && (esbuild = await import('esbuild'));
11
15
  }
12
16
  async function resolve(specifier, context, nextResolve) {
13
17
  if (shouldSkip(specifier))
@@ -22,12 +26,11 @@ async function resolve(specifier, context, nextResolve) {
22
26
  url = pathToFileURL(absPath).href;
23
27
  }
24
28
  let urlObj = new URL(url);
25
- if (inDev) {
29
+ if (isDevelopment()) {
26
30
  let filePath = fileURLToPath(urlObj), version = createVersion(filePath);
27
- urlObj.searchParams.set("hmr", version), parentURL && parentPort && parentPort.postMessage({
28
- type: "dependency",
29
- parent: parentURL,
30
- child: url
31
+ urlObj.searchParams.set("hmr", version), parentURL && rpc.send("dependencyAdd", {
32
+ parentURL,
33
+ url
31
34
  });
32
35
  }
33
36
  return {
@@ -59,8 +62,8 @@ async function load(url, context, nextLoad) {
59
62
  return nextLoad(url, context);
60
63
  }
61
64
  function createVersion(filename) {
62
- let version = versions?.get(filename);
63
- return version || (version = Date.now().toString(), versions?.set(filename, version)), version;
65
+ let version = versions.get(filename);
66
+ return version || (version = Date.now().toString(), versions.set(filename, version)), version;
64
67
  }
65
68
  function shouldSkip(specifier) {
66
69
  if (Module.isBuiltin(specifier) || specifier.includes("/node_modules/"))
@@ -71,19 +74,9 @@ function shouldSkip(specifier) {
71
74
  }
72
75
  return true;
73
76
  }
74
- function onMessage(message) {
75
- let { type, data, id } = message;
76
- if (type !== "unload") {
77
- parentPort?.postMessage({
78
- id,
79
- data: false
80
- });
81
- return;
82
- }
83
- versions?.delete(data), parentPort?.postMessage({
84
- id,
85
- data: true
86
- });
77
+ function onUnload(id, path) {
78
+ let deleted = versions.delete(resolve$1(path));
79
+ rpc.success(id, deleted);
87
80
  }
88
81
 
89
82
  export { initialize, load, resolve };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,9 @@ import * as discord_js from 'discord.js';
2
2
  import { ChatInputCommandInteraction, CacheType, Message, User, MessageCreateOptions, InteractionReplyOptions, Awaitable, Collection, Events, IntentsBitField, ClientEvents, Client, ClientOptions, GatewayIntentBits } from 'discord.js';
3
3
  import { inspect } from 'node:util';
4
4
  import z from 'zod';
5
+ import EventEmitter from 'node:events';
6
+ import { Serializable, ChildProcess } from 'node:child_process';
7
+ import { MessagePort as MessagePort$1 } from 'node:worker_threads';
5
8
 
6
9
  declare enum ParamUserType {
7
10
  Bot = "bot",
@@ -367,12 +370,18 @@ declare class ProjectCacheManager {
367
370
  declare class Instance {
368
371
  client: BakitClient;
369
372
  cache: ProjectCacheManager;
373
+ private rpc;
370
374
  constructor();
371
375
  start(): Promise<void>;
372
376
  private initProcess;
373
377
  private loadModules;
374
378
  private initIntents;
375
- private onProcessMessage;
379
+ private isInHotDirectory;
380
+ private isFileHotReloadable;
381
+ private restart;
382
+ shutdown(): Promise<void>;
383
+ private onFileRemove;
384
+ private onFileChange;
376
385
  }
377
386
  declare function useApp(): Instance;
378
387
 
@@ -449,20 +458,49 @@ declare function extractSnowflakeId(input: string): string | null;
449
458
 
450
459
  declare function getTopLevelDirectory(path: string, entryDir: string): string | null;
451
460
 
452
- interface PostMessage<Data> {
453
- id: number;
461
+ declare function $initLoader(): void;
462
+ declare function $unloadFile(path: string): Promise<boolean>;
463
+ declare function isImported(filePath: string): boolean;
464
+ declare function isImportedBy(filePath: string, matcher: string | RegExp | ((path: string) => boolean)): boolean;
465
+
466
+ declare const RPC_RESPONSE_MARK = "$DONE:";
467
+ declare const RPC_RESPONSE_TIMEOUT = 5000;
468
+ interface BaseRPCMessage {
469
+ id: string;
470
+ }
471
+ interface BaseRPCResponse {
472
+ id: `${typeof RPC_RESPONSE_MARK}${string}`;
473
+ }
474
+ interface RPCRequest<Data extends Serializable = Serializable> extends BaseRPCMessage {
454
475
  type: string;
455
476
  data: Data;
456
477
  }
457
- interface ResponseMessage<Data> {
458
- id: number;
459
- error?: string;
460
- data?: Data;
478
+ interface RPCSuccessResponse<Data extends Serializable = Serializable> extends BaseRPCResponse {
479
+ data: Data;
480
+ }
481
+ interface RPCErrorResponse extends BaseRPCResponse {
482
+ error: string;
483
+ }
484
+ type RPCResponse<Data extends Serializable = Serializable> = RPCSuccessResponse<Data> | RPCErrorResponse;
485
+ type RPCMessage<Data extends Serializable = Serializable> = RPCRequest<Data> | RPCResponse<Data>;
486
+ interface RPCPendingPromise {
487
+ resolve: (data: any) => void;
488
+ reject: (error: unknown) => void;
489
+ timeout: NodeJS.Timeout;
490
+ }
491
+ declare class RPC extends EventEmitter {
492
+ transport: MessagePort$1 | NodeJS.Process | ChildProcess;
493
+ requests: Map<string, RPCPendingPromise>;
494
+ constructor(transport: MessagePort$1 | NodeJS.Process | ChildProcess);
495
+ postMessage(message: Serializable): void;
496
+ private onMessage;
497
+ private handleResponseMessage;
498
+ private handleRequestMessage;
499
+ send<Data extends Serializable>(type: string, data: Data, id?: string): void;
500
+ success<Data extends Serializable>(id: string, data: Data): void;
501
+ error(id: string, error: string): void;
502
+ request<Data extends Serializable, Output extends Serializable>(type: string, data: Data, id?: string): Promise<Output>;
461
503
  }
462
- declare function $initLoader(): void;
463
- declare function $postLoaderMessage<Data>(type: string, data: Data, wait?: false): void;
464
- declare function $postLoaderMessage<Data, Output = unknown>(type: string, data: Data, wait: true): Promise<Output>;
465
- declare function $unloadFile(path: string): Promise<boolean>;
466
504
 
467
505
  declare const messageCommandHandler: Listener<Events.MessageCreate>;
468
506
  declare const chatInputCommandHandler: Listener<Events.InteractionCreate>;
@@ -494,4 +532,36 @@ declare function loadConfig(cwd?: string): Promise<ProjectConfig>;
494
532
  */
495
533
  declare function getConfig(): ProjectConfig;
496
534
 
497
- export { $initLoader, $postLoaderMessage, $unloadFile, type AnyParam, ArgumentError, BakitClient, type BakitClientEvents, BakitError, BaseClientManager, BaseCommandContext, BaseParam, type BaseParamOptions, BaseParamSchema, ChatInputContext, type ChatInputContextSendOptions, Command, type CommandContext, CommandManager, type CommandOptions, type CommandOptionsInput, CommandOptionsSchema, Context, type ContextSendOptions, EVENT_INTENT_MAPPING, type ErrorHookCallback, type GetPrefixFunction, HookOrder, HookState, type InferParamTuple, type InferParamValue, Instance, LifecycleManager, Listener, ListenerManager, type ListenerOptions, ListenerOptionsSchema, type MainHookCallback, MessageContext, type MessageContextSendOptions, type NumberOptions, NumberParam, NumberParamSchema, type ParamResolvedOutputType, ParamUserType, Params, type PostMessage, ProjectCacheManager, type ProjectConfig, type ProjectConfigInput, ProjectConfigSchema, type ResponseMessage, type StringOptions, StringParam, StringParamSchema, type UserOptions, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getTopLevelDirectory, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
535
+ interface InitializeData {
536
+ port: MessagePort;
537
+ }
538
+
539
+ interface ResolveContext {
540
+ conditions: string[];
541
+ importAttributes: Record<string, string>;
542
+ parentURL?: string;
543
+ }
544
+
545
+ interface ResolveResult {
546
+ url: string;
547
+ shortCircuit?: boolean;
548
+ format?: string | null | undefined;
549
+ importAttributes?: Record<string, string>;
550
+ }
551
+
552
+ interface LoadContext {
553
+ conditions: string[];
554
+ format: string | null | undefined;
555
+ importAttributes: Record<string, string>;
556
+ }
557
+
558
+ interface LoadResult {
559
+ source: string | ArrayBuffer | Uint8Array;
560
+ format: string;
561
+ shortCircuit?: boolean;
562
+ }
563
+
564
+ type NextResolve = (specifier: string, context: ResolveContext) => Promise<ResolveResult>;
565
+ type NextLoad = (url: string, context: LoadContext) => Promise<LoadResult>;
566
+
567
+ export { $initLoader, $unloadFile, type AnyParam, ArgumentError, BakitClient, type BakitClientEvents, BakitError, BaseClientManager, BaseCommandContext, BaseParam, type BaseParamOptions, BaseParamSchema, type BaseRPCMessage, type BaseRPCResponse, ChatInputContext, type ChatInputContextSendOptions, Command, type CommandContext, CommandManager, type CommandOptions, type CommandOptionsInput, CommandOptionsSchema, Context, type ContextSendOptions, EVENT_INTENT_MAPPING, type ErrorHookCallback, type GetPrefixFunction, HookOrder, HookState, type InferParamTuple, type InferParamValue, type InitializeData, Instance, LifecycleManager, Listener, ListenerManager, type ListenerOptions, ListenerOptionsSchema, type LoadContext, type LoadResult, type MainHookCallback, MessageContext, type MessageContextSendOptions, type NextLoad, type NextResolve, type NumberOptions, NumberParam, NumberParamSchema, type ParamResolvedOutputType, ParamUserType, Params, ProjectCacheManager, type ProjectConfig, type ProjectConfigInput, ProjectConfigSchema, RPC, type RPCErrorResponse, type RPCMessage, type RPCPendingPromise, type RPCRequest, type RPCResponse, type RPCSuccessResponse, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, type ResolveContext, type ResolveResult, type StringOptions, StringParam, StringParamSchema, type UserOptions, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getTopLevelDirectory, isImported, isImportedBy, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { GatewayIntentBits, Events, Client, Collection, IntentsBitField, SlashCommandBuilder, SlashCommandStringOption, SlashCommandNumberOption, SlashCommandUserOption, ChatInputCommandInteraction, Message } from 'discord.js';
2
2
  import { inspect } from 'util';
3
- import { posix, resolve, relative, sep, join, dirname } from 'path';
3
+ import { pathToFileURL, fileURLToPath } from 'url';
4
+ import { resolve, posix, relative, sep, join, dirname } from 'path';
4
5
  import glob from 'tiny-glob';
5
6
  import z4 from 'zod';
6
7
  import { register } from 'module';
7
8
  import { MessageChannel } from 'worker_threads';
8
- import { pathToFileURL } from 'url';
9
+ import { randomUUID, createHash } from 'crypto';
10
+ import EventEmitter from 'events';
9
11
  import { existsSync, mkdirSync, rmSync } from 'fs';
10
12
  import { mkdir, writeFile, readFile, rm } from 'fs/promises';
11
- import { createHash } from 'crypto';
12
13
 
13
14
  // src/core/client/BakitClient.ts
14
15
  var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z4.object({
@@ -164,34 +165,109 @@ function extractSnowflakeId(input) {
164
165
  function getTopLevelDirectory(path, entryDir) {
165
166
  return relative(entryDir, path).split(sep)[0] ?? null;
166
167
  }
167
- var port1, port2, messageId = 0, pending = /* @__PURE__ */ new Map();
168
+ var RPC_RESPONSE_MARK = "$DONE:", RPC_RESPONSE_TIMEOUT = 5e3, RPC = class extends EventEmitter {
169
+ constructor(transport) {
170
+ super();
171
+ this.transport = transport;
172
+ this.transport.on("message", (message) => this.onMessage(message));
173
+ }
174
+ requests = /* @__PURE__ */ new Map();
175
+ postMessage(message) {
176
+ let { transport } = this;
177
+ "send" in transport ? transport.send(message) : "postMessage" in transport ? transport.postMessage(message) : console.warn(`${transport.constructor.name} doesn't support IPC`);
178
+ }
179
+ onMessage(message) {
180
+ if (message.id.startsWith(RPC_RESPONSE_MARK)) {
181
+ this.handleResponseMessage(message);
182
+ return;
183
+ }
184
+ if ("type" in message) {
185
+ this.handleRequestMessage(message);
186
+ return;
187
+ }
188
+ }
189
+ handleResponseMessage(message) {
190
+ let id = message.id.slice(RPC_RESPONSE_MARK.length), request = this.requests.get(id);
191
+ if (!request)
192
+ return;
193
+ let { reject, resolve: resolve5, timeout } = request;
194
+ this.requests.delete(id), clearTimeout(timeout), "data" in message ? resolve5(message.data) : reject(new Error(message.error));
195
+ }
196
+ handleRequestMessage(message) {
197
+ this.emit("message", message), this.emit(message.type, message.id, message.data);
198
+ }
199
+ send(type, data, id = randomUUID()) {
200
+ let message = {
201
+ id,
202
+ type,
203
+ data
204
+ };
205
+ this.postMessage(message);
206
+ }
207
+ success(id, data) {
208
+ let message = {
209
+ id: `${RPC_RESPONSE_MARK}${id}`,
210
+ data
211
+ };
212
+ this.postMessage(message);
213
+ }
214
+ error(id, error) {
215
+ let message = {
216
+ id: `${RPC_RESPONSE_MARK}${id}`,
217
+ error
218
+ };
219
+ this.postMessage(message);
220
+ }
221
+ request(type, data, id = randomUUID()) {
222
+ return new Promise((resolve5, reject) => {
223
+ let timeout = setTimeout(() => {
224
+ this.requests.delete(id) && reject(new Error("Request timed out"));
225
+ }, RPC_RESPONSE_TIMEOUT);
226
+ this.requests.set(id, {
227
+ resolve: resolve5,
228
+ reject,
229
+ timeout
230
+ }), this.send(type, data, id);
231
+ });
232
+ }
233
+ };
234
+ var rpc, reverseDependencyGraph = /* @__PURE__ */ new Map();
168
235
  function $initLoader() {
169
- let channel = new MessageChannel();
170
- port1 = channel.port1, port2 = channel.port2;
171
- let hookPath = new URL("./hooks.js", import.meta.url).href;
236
+ let { port1, port2 } = new MessageChannel(), hookPath = new URL("./hooks.js", import.meta.url).href;
172
237
  register(hookPath, import.meta.url, {
173
238
  data: { port: port1 },
174
239
  transferList: [port1]
175
- }), port2.on("message", onMessage), port2.unref();
240
+ }), rpc = new RPC(port2), rpc.on("dependencyAdd", (_id, data) => onDependencyAdd(data)), port2.unref();
176
241
  }
177
- function onMessage(message) {
178
- let { id, error, data } = message, request = pending.get(id);
179
- request && (pending.delete(id), error ? request.reject(new Error(error)) : request.resolve(data));
242
+ function $unloadFile(path) {
243
+ if (!rpc)
244
+ throw new Error("Loader isn't initialized");
245
+ return rpc.request("unload", resolve(path));
180
246
  }
181
- function $postLoaderMessage(type, data, wait = false) {
182
- let id = messageId++, message = { id, type, data };
183
- if (!wait) {
184
- port2?.postMessage(message);
185
- return;
247
+ function isImported(filePath) {
248
+ return !!reverseDependencyGraph.get(filePath)?.size;
249
+ }
250
+ function isImportedBy(filePath, matcher) {
251
+ let queue = [filePath], visited = /* @__PURE__ */ new Set();
252
+ for (; queue.length > 0; ) {
253
+ let current = queue.shift();
254
+ if (visited.has(current))
255
+ continue;
256
+ visited.add(current);
257
+ let parents = reverseDependencyGraph.get(current);
258
+ if (parents)
259
+ for (let parent of parents) {
260
+ let isMatch = false;
261
+ if (typeof matcher == "string" ? isMatch = parent === matcher : matcher instanceof RegExp ? isMatch = matcher.test(parent) : typeof matcher == "function" && (isMatch = matcher(parent)), isMatch)
262
+ return true;
263
+ visited.has(parent) || queue.push(parent);
264
+ }
186
265
  }
187
- return new Promise((resolve2, reject) => {
188
- if (!port2)
189
- return reject(new Error("Loader is not initialized"));
190
- pending.set(id, { resolve: resolve2, reject }), port2.postMessage(message);
191
- });
266
+ return false;
192
267
  }
193
- function $unloadFile(path) {
194
- return $postLoaderMessage("unload", resolve(path), true);
268
+ function onDependencyAdd(data) {
269
+ let { url, parentURL } = data, path = fileURLToPath(url), parentPath = fileURLToPath(parentURL), entry = reverseDependencyGraph.get(path);
270
+ entry || (entry = /* @__PURE__ */ new Set(), reverseDependencyGraph.set(path, entry)), entry.add(parentPath);
195
271
  }
196
272
 
197
273
  // src/core/structures/param/Param.ts
@@ -475,6 +551,8 @@ var BaseClientManager = class {
475
551
  this.client = client;
476
552
  }
477
553
  };
554
+
555
+ // src/core/managers/CommandManager.ts
478
556
  var CommandManager = class extends BaseClientManager {
479
557
  commands = new Collection();
480
558
  entries = new Collection();
@@ -488,6 +566,7 @@ var CommandManager = class extends BaseClientManager {
488
566
  * @returns The command object if added successfully.
489
567
  */
490
568
  async load(path) {
569
+ path = resolve(path);
491
570
  let command = (await import(pathToFileURL(path).href)).default;
492
571
  if (!command) {
493
572
  console.warn(`[Loader] File has no default export: ${path}`);
@@ -505,12 +584,13 @@ var CommandManager = class extends BaseClientManager {
505
584
  * @returns The command object if unloaded successfully.
506
585
  */
507
586
  async unload(path) {
587
+ path = resolve(path);
508
588
  let command = this.entries.get(path);
509
589
  if (this.entries.delete(path), await $unloadFile(path), !!command)
510
590
  return this.remove(command);
511
591
  }
512
592
  async reload(path) {
513
- await this.unload(path);
593
+ path = resolve(path), await this.unload(path);
514
594
  let command = await this.load(path);
515
595
  if (command)
516
596
  return console.log(`[Loader] Reloaded command '${command.options.name}' at '${path}'`), command;
@@ -571,6 +651,8 @@ var Context = class {
571
651
  this.canceled = true;
572
652
  }
573
653
  };
654
+
655
+ // src/core/managers/ListenerManager.ts
574
656
  var ListenerManager = class extends BaseClientManager {
575
657
  listeners = [];
576
658
  entries = new Collection();
@@ -585,6 +667,7 @@ var ListenerManager = class extends BaseClientManager {
585
667
  * @returns The listener object if added successfully.
586
668
  */
587
669
  async load(path) {
670
+ path = resolve(path);
588
671
  let listener = (await import(pathToFileURL(path).href)).default;
589
672
  if (!listener) {
590
673
  console.warn(`[Loader] File has no default export: ${path}`);
@@ -602,12 +685,13 @@ var ListenerManager = class extends BaseClientManager {
602
685
  * @returns The listener object if unloaded successfully.
603
686
  */
604
687
  async unload(path) {
688
+ path = resolve(path);
605
689
  let listener = this.entries.get(path);
606
690
  if (this.entries.delete(path), await $unloadFile(path), !!listener)
607
691
  return this.remove(listener)?.[0];
608
692
  }
609
693
  async reload(path) {
610
- await this.unload(path);
694
+ path = resolve(path), await this.unload(path);
611
695
  let listener = await this.load(path);
612
696
  if (listener)
613
697
  return console.log(`[Loader] Reloaded listener '${listener.options.name}' at '${path}'`), listener;
@@ -766,7 +850,7 @@ var BaseCommandContext = class extends Context {
766
850
  return await channel.send(options);
767
851
  }
768
852
  };
769
- var ProjectConfigSchema = z4.object({
853
+ var CONFIG_EXTENSIONS = ["ts", "js"], ProjectConfigSchema = z4.object({
770
854
  /**
771
855
  * The gateway intents to use for the Discord client.
772
856
  *
@@ -785,7 +869,13 @@ var ProjectConfigSchema = z4.object({
785
869
  * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
786
870
  */
787
871
  clientOptions: z4.custom().optional(),
872
+ /**
873
+ * Your bot prefixes to trigger the commands.
874
+ */
788
875
  prefixes: z4.array(z4.string()).default([]),
876
+ /**
877
+ * Your Discord bot token.
878
+ */
789
879
  token: z4.string()
790
880
  });
791
881
  function defineConfig(config) {
@@ -795,7 +885,7 @@ var _config;
795
885
  async function loadConfig(cwd = process.cwd()) {
796
886
  if (_config)
797
887
  return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
798
- let globPattern = `bakit.config.{${["ts", "js"].join(",")}}`, [configPath, other] = await glob(globPattern, {
888
+ let globPattern = `bakit.config.{${CONFIG_EXTENSIONS.join(",")}}`, [configPath, other] = await glob(globPattern, {
799
889
  cwd: cwd.replace(/\\/g, "/"),
800
890
  // ensure the path uses `/` instead of `\` on Windows
801
891
  absolute: true
@@ -811,6 +901,39 @@ function getConfig() {
811
901
  throw new Error("Project config is not loaded.");
812
902
  return _config;
813
903
  }
904
+ var ProjectCacheManager = class {
905
+ rootDir;
906
+ constructor(root = process.cwd()) {
907
+ this.rootDir = join(root, ".bakit"), this.ensureRoot();
908
+ }
909
+ ensureRoot() {
910
+ existsSync(this.rootDir) || mkdirSync(this.rootDir, { recursive: true });
911
+ }
912
+ getHash(data) {
913
+ return createHash("sha256").update(JSON.stringify(data)).digest("hex");
914
+ }
915
+ async write(path, data) {
916
+ let fullPath = join(this.rootDir, path), dir = dirname(fullPath);
917
+ await mkdir(dir, { recursive: true });
918
+ let content = typeof data == "string" ? data : JSON.stringify(data);
919
+ await writeFile(fullPath, content, "utf-8");
920
+ }
921
+ async read(path) {
922
+ let fullPath = join(this.rootDir, path);
923
+ try {
924
+ let content = await readFile(fullPath, "utf-8");
925
+ return JSON.parse(content);
926
+ } catch {
927
+ return null;
928
+ }
929
+ }
930
+ async clear() {
931
+ await rm(this.rootDir, { recursive: true, force: true });
932
+ }
933
+ clearSync() {
934
+ existsSync(this.rootDir) && rmSync(this.rootDir, { recursive: true, force: true });
935
+ }
936
+ };
814
937
  var messageCommandHandler = defineListener(Events.MessageCreate), chatInputCommandHandler = defineListener(Events.InteractionCreate), registerCommandsHandler = defineListener({
815
938
  name: Events.ClientReady,
816
939
  once: true
@@ -869,46 +992,14 @@ chatInputCommandHandler.main(async (_, interaction) => {
869
992
  }
870
993
  await command.execute(context, ...resolvedArgs);
871
994
  });
872
- var ProjectCacheManager = class {
873
- rootDir;
874
- constructor(root = process.cwd()) {
875
- this.rootDir = join(root, ".bakit"), this.ensureRoot();
876
- }
877
- ensureRoot() {
878
- existsSync(this.rootDir) || mkdirSync(this.rootDir, { recursive: true });
879
- }
880
- getHash(data) {
881
- return createHash("sha256").update(JSON.stringify(data)).digest("hex");
882
- }
883
- async write(path, data) {
884
- let fullPath = join(this.rootDir, path), dir = dirname(fullPath);
885
- await mkdir(dir, { recursive: true });
886
- let content = typeof data == "string" ? data : JSON.stringify(data);
887
- await writeFile(fullPath, content, "utf-8");
888
- }
889
- async read(path) {
890
- let fullPath = join(this.rootDir, path);
891
- try {
892
- let content = await readFile(fullPath, "utf-8");
893
- return JSON.parse(content);
894
- } catch {
895
- return null;
896
- }
897
- }
898
- async clear() {
899
- await rm(this.rootDir, { recursive: true, force: true });
900
- }
901
- clearSync() {
902
- existsSync(this.rootDir) && rmSync(this.rootDir, { recursive: true, force: true });
903
- }
904
- };
905
995
 
906
996
  // src/core/internal/Instance.ts
907
- var Instance = class {
997
+ var HOT_DIRECTORIES = ["listeners", "commands"], SOURCE_ROOT = resolve(process.cwd(), "src"), Instance = class {
908
998
  client;
909
999
  cache;
1000
+ rpc;
910
1001
  constructor() {
911
- this.cache = new ProjectCacheManager();
1002
+ this.cache = new ProjectCacheManager(), this.rpc = new RPC(process);
912
1003
  }
913
1004
  async start() {
914
1005
  await loadConfig();
@@ -922,7 +1013,7 @@ var Instance = class {
922
1013
  ), await this.loadModules(), this.initIntents(), await this.client.login(config.token), this.initProcess();
923
1014
  }
924
1015
  initProcess() {
925
- process.on("message", (msg) => this.onProcessMessage(msg));
1016
+ process.env.NODE_ENV === "development" && (this.rpc.on("fileRemove", (_id, path) => this.onFileRemove(path)), this.rpc.on("fileChange", (_id, path) => this.onFileChange(path))), process.on("SIGINT", () => this.shutdown()), process.on("SIGTERM", () => this.shutdown());
926
1017
  }
927
1018
  loadModules() {
928
1019
  let { managers } = this.client, { commands, listeners } = managers;
@@ -932,18 +1023,52 @@ var Instance = class {
932
1023
  let config = getConfig(), { options, managers } = this.client, { listeners } = managers, intents;
933
1024
  config.intents === "auto" ? intents = listeners.getNeededIntents() : (intents = listeners.getBaseIntents(), typeof config.intents == "bigint" ? intents.bitfield = Number(config.intents) : intents.add(...config.intents)), options.intents = intents;
934
1025
  }
935
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
936
- async onProcessMessage(message) {
937
- let { type, url } = message;
938
- if (!type.startsWith("hmr:"))
1026
+ isInHotDirectory(path) {
1027
+ if (!path.startsWith(SOURCE_ROOT))
1028
+ return false;
1029
+ let topLevelDir = getTopLevelDirectory(path, SOURCE_ROOT);
1030
+ return !!topLevelDir && HOT_DIRECTORIES.includes(topLevelDir);
1031
+ }
1032
+ isFileHotReloadable(path) {
1033
+ return path = resolve(path), !(!this.isInHotDirectory(path) || isImportedBy(path, (parentPath) => !this.isInHotDirectory(parentPath)));
1034
+ }
1035
+ restart() {
1036
+ this.rpc.send("restart", {});
1037
+ }
1038
+ async shutdown() {
1039
+ this.client && await this.client.destroy().catch(() => null), process.exit(0);
1040
+ }
1041
+ async onFileRemove(path) {
1042
+ if (!isImported(path))
1043
+ return;
1044
+ if (!this.isFileHotReloadable(path)) {
1045
+ this.restart();
1046
+ return;
1047
+ }
1048
+ let topLevelDir = getTopLevelDirectory(path, SOURCE_ROOT), { listeners, commands } = this.client.managers;
1049
+ switch (topLevelDir) {
1050
+ case "listeners":
1051
+ await listeners.unload(path);
1052
+ break;
1053
+ case "commands":
1054
+ await commands.unload(path);
1055
+ break;
1056
+ }
1057
+ }
1058
+ async onFileChange(path) {
1059
+ if (!isImported(path))
1060
+ return;
1061
+ if (!this.isFileHotReloadable(path)) {
1062
+ this.restart();
939
1063
  return;
940
- let target = type.split(":")[1], { listeners, commands } = this.client.managers;
941
- switch (target) {
1064
+ }
1065
+ let topLevelDir = getTopLevelDirectory(path, SOURCE_ROOT), { listeners, commands } = this.client.managers;
1066
+ switch (topLevelDir) {
942
1067
  case "listeners":
943
- await listeners.reload(url);
1068
+ await listeners.reload(path);
944
1069
  break;
945
1070
  case "commands":
946
- await commands.reload(url);
1071
+ await commands.reload(path);
947
1072
  break;
948
1073
  }
949
1074
  }
@@ -962,4 +1087,4 @@ var Params = {
962
1087
  user: createFactory(UserParam)
963
1088
  };
964
1089
 
965
- export { $initLoader, $postLoaderMessage, $unloadFile, ArgumentError, BakitClient, BakitError, BaseClientManager, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Context, EVENT_INTENT_MAPPING, HookOrder, HookState, Instance, LifecycleManager, Listener, ListenerManager, ListenerOptionsSchema, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectCacheManager, ProjectConfigSchema, StringParam, StringParamSchema, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getTopLevelDirectory, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
1090
+ export { $initLoader, $unloadFile, ArgumentError, BakitClient, BakitError, BaseClientManager, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Context, EVENT_INTENT_MAPPING, HookOrder, HookState, Instance, LifecycleManager, Listener, ListenerManager, ListenerOptionsSchema, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectCacheManager, ProjectConfigSchema, RPC, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, StringParam, StringParamSchema, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getTopLevelDirectory, isImported, isImportedBy, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bakit",
3
- "version": "2.0.0-alpha.23",
3
+ "version": "2.0.0-alpha.25",
4
4
  "description": "A framework for discord.js",
5
5
  "type": "module",
6
6
  "exports": {