bakit 2.0.0-alpha.24 → 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,71 +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, relative, sep } from 'path';
4
+ import path, { resolve } from 'path';
5
5
  import chokidar from 'chokidar';
6
+ import { RPC } from 'bakit';
6
7
 
7
8
  // src/cli/bin.ts
8
- function getTopLevelDirectory(path2, entryDir) {
9
- return relative(entryDir, path2).split(sep)[0] ?? null;
10
- }
11
-
12
- // src/cli/process/DevProcessManager.ts
13
9
  var DevProcessManager = class {
14
10
  constructor(options) {
15
11
  this.options = options;
16
12
  }
17
- child = null;
13
+ rpc = null;
18
14
  restartTimer = null;
19
15
  start() {
20
16
  console.log("Starting bakit in dev mode..."), this.startChild(), this.startWatcher();
21
17
  }
22
18
  startChild() {
23
- if (this.child) return;
24
- let entry = path.resolve(this.options.entry);
25
- this.child = fork(entry, {
19
+ if (this.rpc)
20
+ return;
21
+ let entry = path.resolve(this.options.entry), child = fork(entry, {
26
22
  execArgv: ["--import", "bakit/register"],
27
23
  stdio: "inherit",
28
24
  env: {
29
25
  ...process.env,
30
26
  NODE_ENV: "development"
31
27
  }
32
- }), this.child.on("exit", () => {
33
- this.child = null;
34
28
  });
29
+ this.rpc = new RPC(child), this.rpc.on("restart", (fileUpdated) => this.scheduleRestart(fileUpdated));
35
30
  }
36
31
  restartChild() {
37
- if (!this.child)
32
+ if (!this.rpc)
38
33
  return this.startChild();
39
- let old = this.child;
40
- old.once("exit", () => {
41
- this.child = null, this.startChild();
42
- }), old.kill("SIGTERM");
34
+ let child = this.rpc.transport;
35
+ child.once("exit", () => {
36
+ this.rpc = null, this.startChild();
37
+ }), child.kill("SIGTERM");
43
38
  }
44
39
  startWatcher() {
45
- let { rootDir } = this.options;
46
- chokidar.watch(rootDir, {
40
+ let { rootDir } = this.options, watcher = chokidar.watch(rootDir, {
47
41
  ignoreInitial: true,
48
42
  awaitWriteFinish: {
49
43
  stabilityThreshold: 200,
50
44
  pollInterval: 50
51
45
  }
52
- }).on("change", (path2) => {
53
- this.onFileChanged(path2);
54
46
  });
47
+ watcher.on("change", (path2) => this.onFileUpdate("fileChange", path2)), watcher.on("unlink", (path2) => this.onFileUpdate("fileRemove", path2));
55
48
  }
56
- onFileChanged(path2) {
57
- if (!this.child)
58
- return;
59
- let top = getTopLevelDirectory(path2, this.options.rootDir);
60
- if (top && this.options.hotDirs.includes(top)) {
61
- this.child.connected && this.child.send({ type: `hmr:${top}`, path: resolve(path2) });
62
- return;
49
+ onFileUpdate(type, path2) {
50
+ try {
51
+ this.rpc?.send(type, resolve(path2));
52
+ } catch {
53
+ this.scheduleRestart(true);
63
54
  }
64
- this.scheduleRestart();
65
55
  }
66
- scheduleRestart() {
56
+ scheduleRestart(fileUpdated = false) {
67
57
  this.restartTimer && clearTimeout(this.restartTimer), this.restartTimer = setTimeout(() => {
68
- console.log("Detected changes, restarting..."), this.restartChild(), this.restartTimer = null;
58
+ fileUpdated && console.log("File changes detected, restarting..."), this.restartChild(), this.restartTimer = null;
69
59
  }, 150);
70
60
  }
71
61
  };
package/dist/hooks.js CHANGED
@@ -3,14 +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"], parentPort, versions, esbuild;
9
+ var EXTENSIONS = [".js", ".ts"], rpc, versions, esbuild;
9
10
  function isDevelopment() {
10
11
  return process.env.NODE_ENV === "development";
11
12
  }
12
13
  async function initialize({ port }) {
13
- isDevelopment() && (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'));
14
15
  }
15
16
  async function resolve(specifier, context, nextResolve) {
16
17
  if (shouldSkip(specifier))
@@ -27,10 +28,9 @@ async function resolve(specifier, context, nextResolve) {
27
28
  let urlObj = new URL(url);
28
29
  if (isDevelopment()) {
29
30
  let filePath = fileURLToPath(urlObj), version = createVersion(filePath);
30
- urlObj.searchParams.set("hmr", version), parentURL && parentPort && parentPort.postMessage({
31
- type: "dependency",
32
- parent: parentURL,
33
- child: url
31
+ urlObj.searchParams.set("hmr", version), parentURL && rpc.send("dependencyAdd", {
32
+ parentURL,
33
+ url
34
34
  });
35
35
  }
36
36
  return {
@@ -62,8 +62,8 @@ async function load(url, context, nextLoad) {
62
62
  return nextLoad(url, context);
63
63
  }
64
64
  function createVersion(filename) {
65
- let version = versions?.get(filename);
66
- 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;
67
67
  }
68
68
  function shouldSkip(specifier) {
69
69
  if (Module.isBuiltin(specifier) || specifier.includes("/node_modules/"))
@@ -74,19 +74,9 @@ function shouldSkip(specifier) {
74
74
  }
75
75
  return true;
76
76
  }
77
- function onMessage(message) {
78
- let { type, data, id } = message;
79
- if (type !== "unload") {
80
- parentPort?.postMessage({
81
- id,
82
- data: false
83
- });
84
- return;
85
- }
86
- versions?.delete(data), parentPort?.postMessage({
87
- id,
88
- data: true
89
- });
77
+ function onUnload(id, path) {
78
+ let deleted = versions.delete(resolve$1(path));
79
+ rpc.success(id, deleted);
90
80
  }
91
81
 
92
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((resolve4, reject) => {
188
- if (!port2)
189
- return reject(new Error("Loader is not initialized"));
190
- pending.set(id, { resolve: resolve4, 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();
@@ -573,6 +651,8 @@ var Context = class {
573
651
  this.canceled = true;
574
652
  }
575
653
  };
654
+
655
+ // src/core/managers/ListenerManager.ts
576
656
  var ListenerManager = class extends BaseClientManager {
577
657
  listeners = [];
578
658
  entries = new Collection();
@@ -770,7 +850,7 @@ var BaseCommandContext = class extends Context {
770
850
  return await channel.send(options);
771
851
  }
772
852
  };
773
- var ProjectConfigSchema = z4.object({
853
+ var CONFIG_EXTENSIONS = ["ts", "js"], ProjectConfigSchema = z4.object({
774
854
  /**
775
855
  * The gateway intents to use for the Discord client.
776
856
  *
@@ -789,7 +869,13 @@ var ProjectConfigSchema = z4.object({
789
869
  * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
790
870
  */
791
871
  clientOptions: z4.custom().optional(),
872
+ /**
873
+ * Your bot prefixes to trigger the commands.
874
+ */
792
875
  prefixes: z4.array(z4.string()).default([]),
876
+ /**
877
+ * Your Discord bot token.
878
+ */
793
879
  token: z4.string()
794
880
  });
795
881
  function defineConfig(config) {
@@ -799,7 +885,7 @@ var _config;
799
885
  async function loadConfig(cwd = process.cwd()) {
800
886
  if (_config)
801
887
  return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
802
- 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, {
803
889
  cwd: cwd.replace(/\\/g, "/"),
804
890
  // ensure the path uses `/` instead of `\` on Windows
805
891
  absolute: true
@@ -815,6 +901,39 @@ function getConfig() {
815
901
  throw new Error("Project config is not loaded.");
816
902
  return _config;
817
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
+ };
818
937
  var messageCommandHandler = defineListener(Events.MessageCreate), chatInputCommandHandler = defineListener(Events.InteractionCreate), registerCommandsHandler = defineListener({
819
938
  name: Events.ClientReady,
820
939
  once: true
@@ -873,46 +992,14 @@ chatInputCommandHandler.main(async (_, interaction) => {
873
992
  }
874
993
  await command.execute(context, ...resolvedArgs);
875
994
  });
876
- var ProjectCacheManager = class {
877
- rootDir;
878
- constructor(root = process.cwd()) {
879
- this.rootDir = join(root, ".bakit"), this.ensureRoot();
880
- }
881
- ensureRoot() {
882
- existsSync(this.rootDir) || mkdirSync(this.rootDir, { recursive: true });
883
- }
884
- getHash(data) {
885
- return createHash("sha256").update(JSON.stringify(data)).digest("hex");
886
- }
887
- async write(path, data) {
888
- let fullPath = join(this.rootDir, path), dir = dirname(fullPath);
889
- await mkdir(dir, { recursive: true });
890
- let content = typeof data == "string" ? data : JSON.stringify(data);
891
- await writeFile(fullPath, content, "utf-8");
892
- }
893
- async read(path) {
894
- let fullPath = join(this.rootDir, path);
895
- try {
896
- let content = await readFile(fullPath, "utf-8");
897
- return JSON.parse(content);
898
- } catch {
899
- return null;
900
- }
901
- }
902
- async clear() {
903
- await rm(this.rootDir, { recursive: true, force: true });
904
- }
905
- clearSync() {
906
- existsSync(this.rootDir) && rmSync(this.rootDir, { recursive: true, force: true });
907
- }
908
- };
909
995
 
910
996
  // src/core/internal/Instance.ts
911
- var Instance = class {
997
+ var HOT_DIRECTORIES = ["listeners", "commands"], SOURCE_ROOT = resolve(process.cwd(), "src"), Instance = class {
912
998
  client;
913
999
  cache;
1000
+ rpc;
914
1001
  constructor() {
915
- this.cache = new ProjectCacheManager();
1002
+ this.cache = new ProjectCacheManager(), this.rpc = new RPC(process);
916
1003
  }
917
1004
  async start() {
918
1005
  await loadConfig();
@@ -926,7 +1013,7 @@ var Instance = class {
926
1013
  ), await this.loadModules(), this.initIntents(), await this.client.login(config.token), this.initProcess();
927
1014
  }
928
1015
  initProcess() {
929
- 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());
930
1017
  }
931
1018
  loadModules() {
932
1019
  let { managers } = this.client, { commands, listeners } = managers;
@@ -936,13 +1023,47 @@ var Instance = class {
936
1023
  let config = getConfig(), { options, managers } = this.client, { listeners } = managers, intents;
937
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;
938
1025
  }
939
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
940
- async onProcessMessage(message) {
941
- let { type, path } = message;
942
- 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();
943
1063
  return;
944
- let target = type.split(":")[1], { listeners, commands } = this.client.managers;
945
- switch (target) {
1064
+ }
1065
+ let topLevelDir = getTopLevelDirectory(path, SOURCE_ROOT), { listeners, commands } = this.client.managers;
1066
+ switch (topLevelDir) {
946
1067
  case "listeners":
947
1068
  await listeners.reload(path);
948
1069
  break;
@@ -966,4 +1087,4 @@ var Params = {
966
1087
  user: createFactory(UserParam)
967
1088
  };
968
1089
 
969
- 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.24",
3
+ "version": "2.0.0-alpha.25",
4
4
  "description": "A framework for discord.js",
5
5
  "type": "module",
6
6
  "exports": {