@seedcord/gateway 0.1.0-next.1 → 0.1.0-next.2

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/index.mjs CHANGED
@@ -1,21 +1,24 @@
1
1
  import "reflect-metadata";
2
+ import { installNodeDefaults } from "@seedcord/logger/node";
2
3
  import { BuilderComponent, CustomId, DispatchContext, Fault, Notice, RegisterCommand, RowComponent, Silence, defineGate, paginate } from "@seedcord/core";
3
- import { CommandMetadataKey, ComponentDefsKey, EventMetadataKey, GateNotice, GatedMetadataKey, InteractionMetadataKey, InteractionRouteKeys, InteractionRoutes, MiddlewareMetadataKey, NotInGuild, NoticeCard, PAGE_MAX, SubscribeMetadataKey, decodeFor, pageCursor, pickNotice, prefixOf, routeIdOf, runHandlerGates, setBotColor } from "@seedcord/core/internal";
4
+ import { CommandMetadataKey, ComponentDefsKey, EventMetadataKey, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionRouteKeys, InteractionRoutes, MiddlewareMetadataKey, NotInGuild, NoticeCard, PAGE_MAX, SubscribeMetadataKey, decodeFor, getDevChannel, pageCursor, pickNotice, prefixOf, routeIdOf, runHandlerGates, setBotColor } from "@seedcord/core/internal";
4
5
  import { ApplicationCommandType, AttachmentBuilder, ButtonStyle, Client, Collection, ComponentType, DiscordAPIError, Events, Guild, GuildMember, Message, MessageFlags, PermissionFlagsBits, RESTJSONErrorCodes, Role, SeparatorSpacingSize, SnowflakeUtil, TextChannel, User, WebhookClient, chatInputApplicationCommandMention } from "discord.js";
5
6
  import { SeedcordErrorCode } from "@seedcord/errors";
6
7
  import { SeedcordError, SeedcordRangeError, SeedcordTypeError, validateDiscordToken } from "@seedcord/errors/internal";
7
- import { filterCirculars, formatFilePath, hasKeys, prettify, traverseDirectory } from "@seedcord/utils";
8
- import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Logger, ShutdownPhase, StartupPhase } from "@seedcord/services";
9
- import chalk from "chalk";
8
+ import { filterCirculars, formatFilePath, hasKeys, prettify } from "@seedcord/utils";
9
+ import { Logger, LoggerChannelRegistry, paint } from "@seedcord/logger";
10
10
  import { ActionRowBuilder, ButtonBuilder, ContextMenuCommandBuilder, SlashCommandBuilder, TextDisplayBuilder } from "@discordjs/builders";
11
11
  import { buildSlashRoute, routeLeavesOf } from "@seedcord/utils/internal";
12
12
  import { TypedEventEmitter } from "@seedcord/event-emitter";
13
- import { existsSync } from "node:fs";
14
- import { resolve } from "node:path";
15
- import { pathToFileURL } from "node:url";
16
13
  import { MemoryRateLimiter } from "@seedcord/rate-limiter";
17
- import { SeedcordBrand, wrapHot } from "@seedcord/types/internal";
18
- import { Envapt, Envapter } from "envapt";
14
+ import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, ShutdownPhase, StartupPhase } from "@seedcord/services";
15
+ import { SeedcordBrand } from "@seedcord/types/internal";
16
+ import { Envapter } from "envapt";
17
+ import chalk from "chalk";
18
+ import { Envapt } from "envapt/legacy";
19
+ import { resolve } from "node:path";
20
+ import { HmrModuleHandler } from "@seedcord/core/hmr";
21
+ import { traverseDirectory } from "@seedcord/utils/node";
19
22
  import * as crypto from "node:crypto";
20
23
 
21
24
  export * from "@seedcord/core"
@@ -24,12 +27,18 @@ export * from "@seedcord/errors"
24
27
 
25
28
  export * from "@seedcord/utils"
26
29
 
27
- export * from "@seedcord/services"
30
+ export * from "@seedcord/logger"
28
31
 
29
32
  export * from "@seedcord/event-emitter"
30
33
 
31
34
  export * from "@seedcord/rate-limiter"
32
35
 
36
+ export * from "@seedcord/services"
37
+
38
+ //#region src/logger-bootstrap.ts
39
+ installNodeDefaults();
40
+
41
+ //#endregion
33
42
  //#region src/bot/decorators/Events.ts
34
43
  /**
35
44
  * Registers an event handler class with one or more Discord.js events.
@@ -673,7 +682,7 @@ var EmojiInjector = class {
673
682
  async init() {
674
683
  this.clearEmojis();
675
684
  if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
676
- this.logger.debug(chalk.bold.yellow("No emojis configured, skipping emoji injection."));
685
+ this.logger.debug("No emojis configured, skipping emoji injection.");
677
686
  return;
678
687
  }
679
688
  const configEmojis = this.core.config.bot.emojis;
@@ -1230,16 +1239,6 @@ async function getConfirmation(interaction, prompt, options) {
1230
1239
  return confirmed;
1231
1240
  }
1232
1241
 
1233
- //#endregion
1234
- //#region src/hmr/devChannel.ts
1235
- let channel;
1236
- function setDevChannel(next) {
1237
- channel = next;
1238
- }
1239
- function getDevChannel() {
1240
- return channel;
1241
- }
1242
-
1243
1242
  //#endregion
1244
1243
  //#region src/interfaces/Plugin.ts
1245
1244
  /**
@@ -2160,171 +2159,6 @@ var WebhookLog = class extends Subscriber {
2160
2159
  }
2161
2160
  };
2162
2161
 
2163
- //#endregion
2164
- //#region src/hmr/HmrModuleHandler.ts
2165
- /**
2166
- * Reloads THandler and optional TMiddleware modules on HMR updates.
2167
- *
2168
- * You can either implement a custom `onHmr()` method in your class by extending `HmrAware`, or if it's a plugin, override the existing `onHmr()` with fully custom logic. Or, use this class's `handle()` method inside your `onHmr()` to get standard HMR handling for your modules based on the provided options you give to the constructor.
2169
- *
2170
- * This is only useful during development.
2171
- */
2172
- var HmrModuleHandler = class {
2173
- store;
2174
- options;
2175
- constructor(options) {
2176
- this.options = {
2177
- ...options,
2178
- logger: options.logger.inChannel("hmr")
2179
- };
2180
- this.store = {
2181
- fileToHandlers: /* @__PURE__ */ new Map(),
2182
- fileToMiddlewares: /* @__PURE__ */ new Map(),
2183
- handlerArtifacts: /* @__PURE__ */ new Map()
2184
- };
2185
- }
2186
- /**
2187
- * Handles an HMR update event by reloading affected modules based on the event details and config. It receives an {@link HmrUpdateEvent}, then checks the type of update.
2188
- *
2189
- * - If it's a deletion, it unloads the module.
2190
- * - If it's an update/creation, it checks if the file is tracked, unloads it if so, then reloads the file if it is in the handlers or middlewares directory and is a valid handler/middleware.
2191
- */
2192
- async handle(event) {
2193
- const { file, affectedModules, type } = event;
2194
- const { logger, handlersDir, middlewaresDir } = this.options;
2195
- if (type === "delete" || type === "deleteDir") {
2196
- this.unload(file);
2197
- return;
2198
- }
2199
- if (type === "update" && !existsSync(file)) {
2200
- if (this.isTracked(file)) {
2201
- logger.info(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);
2202
- this.unload(file);
2203
- }
2204
- return;
2205
- }
2206
- const rollbackEnabled = event.rollback ?? true;
2207
- const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];
2208
- const absHandlersDir = resolve(process.cwd(), handlersDir);
2209
- const absMiddlewaresDir = middlewaresDir ? resolve(process.cwd(), middlewaresDir) : null;
2210
- for (const fileToReload of filesToReload) {
2211
- const isHandler = fileToReload.startsWith(absHandlersDir);
2212
- const isMiddleware = absMiddlewaresDir ? fileToReload.startsWith(absMiddlewaresDir) : false;
2213
- if (!isHandler && !isMiddleware) continue;
2214
- await this.reloadWithRollback(fileToReload, rollbackEnabled);
2215
- }
2216
- }
2217
- async reloadWithRollback(file, rollbackEnabled) {
2218
- const { logger } = this.options;
2219
- const snapshot = this.snapshotUnits(file);
2220
- this.unload(file);
2221
- if (await this.reloadFile(file)) return;
2222
- this.unload(file);
2223
- if (!rollbackEnabled) return;
2224
- this.restoreUnits(file, snapshot);
2225
- logger.warn(`${chalk.yellow.bold("Rolled back")} ${chalk.gray(formatFilePath(file))} to the last-good version`);
2226
- }
2227
- /**
2228
- * Tracks a handler module as being loaded from a specific file.
2229
- * @param file - The file path the handler was loaded from.
2230
- * @param handler - The handler module instance.
2231
- */
2232
- trackHandler(file, handler) {
2233
- let set = this.store.fileToHandlers.get(file);
2234
- if (!set) {
2235
- set = /* @__PURE__ */ new Set();
2236
- this.store.fileToHandlers.set(file, set);
2237
- }
2238
- set.add(handler);
2239
- if (this.options.getArtifacts) {
2240
- const artifacts = this.options.getArtifacts(handler);
2241
- this.store.handlerArtifacts.set(handler, artifacts);
2242
- }
2243
- }
2244
- /**
2245
- * Tracks a middleware module as being loaded from a specific file.
2246
- * @param file - The file path the middleware was loaded from.
2247
- * @param middleware - The middleware module instance.
2248
- */
2249
- trackMiddleware(file, middleware) {
2250
- let set = this.store.fileToMiddlewares.get(file);
2251
- if (!set) {
2252
- set = /* @__PURE__ */ new Set();
2253
- this.store.fileToMiddlewares.set(file, set);
2254
- }
2255
- set.add(middleware);
2256
- }
2257
- isTracked(file) {
2258
- return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);
2259
- }
2260
- unload(file) {
2261
- const { logger } = this.options;
2262
- const handlers = this.store.fileToHandlers.get(file);
2263
- if (handlers) {
2264
- for (const handler of handlers) {
2265
- const artifacts = this.store.handlerArtifacts.get(handler);
2266
- this.options.unregisterHandler(handler, artifacts);
2267
- this.store.handlerArtifacts.delete(handler);
2268
- const name = handler.name ?? "Handler";
2269
- logger.info(`${chalk.red.bold("Unloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
2270
- }
2271
- this.store.fileToHandlers.delete(file);
2272
- }
2273
- const middlewares = this.store.fileToMiddlewares.get(file);
2274
- if (middlewares) {
2275
- for (const middleware of middlewares) {
2276
- this.options.unregisterMiddleware?.(middleware);
2277
- const name = middleware.name ?? "Middleware";
2278
- logger.info(`${chalk.red.bold("Unloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
2279
- }
2280
- this.store.fileToMiddlewares.delete(file);
2281
- }
2282
- }
2283
- async reloadFile(file) {
2284
- const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;
2285
- if (!existsSync(file)) {
2286
- logger.info(`File does not exist, skipping reload: ${formatFilePath(file)}`);
2287
- return false;
2288
- }
2289
- try {
2290
- let fileUrl = pathToFileURL(file).href;
2291
- if (process.env.VITEST === "true") fileUrl += `?update=${Date.now()}`;
2292
- const imported = await import(fileUrl);
2293
- for (const val of Object.values(imported)) if (isHandler(val)) {
2294
- registerHandler(val, file);
2295
- this.trackHandler(file, val);
2296
- const name = val.name ?? "Handler";
2297
- logger.info(`${chalk.blue.bold("Reloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
2298
- } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {
2299
- registerMiddleware(val, file);
2300
- this.trackMiddleware(file, val);
2301
- const name = val.name ?? "Middleware";
2302
- logger.info(`${chalk.blue.bold("Reloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
2303
- }
2304
- return true;
2305
- } catch (error) {
2306
- logger.error(`Failed to reload file: ${file}`, error);
2307
- return false;
2308
- }
2309
- }
2310
- snapshotUnits(file) {
2311
- return {
2312
- handlers: [...this.store.fileToHandlers.get(file) ?? []],
2313
- middlewares: [...this.store.fileToMiddlewares.get(file) ?? []]
2314
- };
2315
- }
2316
- restoreUnits(file, snapshot) {
2317
- for (const handler of snapshot.handlers) {
2318
- this.options.registerHandler(handler, file);
2319
- this.trackHandler(file, handler);
2320
- }
2321
- if (this.options.registerMiddleware) for (const middleware of snapshot.middlewares) {
2322
- this.options.registerMiddleware(middleware, file);
2323
- this.trackMiddleware(file, middleware);
2324
- }
2325
- }
2326
- };
2327
-
2328
2162
  //#endregion
2329
2163
  //#region src/bot/utilities/miscellaneous/contextMenuLeaves.ts
2330
2164
  /**
@@ -2385,6 +2219,8 @@ var CommandRegistry = class {
2385
2219
  globalCommands = [];
2386
2220
  guildCommands = new Collection();
2387
2221
  ctorToCommand = /* @__PURE__ */ new Map();
2222
+ loading = false;
2223
+ loadedCommands = [];
2388
2224
  hmrHandler;
2389
2225
  pendingEvents = /* @__PURE__ */ new Map();
2390
2226
  constructor(core, onDeployed) {
@@ -2411,16 +2247,33 @@ var CommandRegistry = class {
2411
2247
  this.isInitialised = true;
2412
2248
  const commandsDir = this.core.config.bot.commands.path;
2413
2249
  if (!commandsDir) return;
2414
- this.logger.info(chalk.bold(commandsDir));
2415
- await this.loadCommands(commandsDir);
2416
- this.logger.utils.summary("Loaded commands", {
2417
- global: this.globalCommands.length,
2418
- "guild groups": this.guildCommands.size
2419
- });
2250
+ this.loading = true;
2251
+ this.loadedCommands.length = 0;
2252
+ try {
2253
+ await this.loadCommands(commandsDir);
2254
+ } finally {
2255
+ this.loading = false;
2256
+ }
2257
+ this.reportLoad();
2420
2258
  getDevChannel()?.on("seedcord:refresh-commands", (data) => {
2421
2259
  this.refresh(data.shouldRefresh);
2422
2260
  });
2423
2261
  }
2262
+ reportLoad() {
2263
+ const { utils } = this.logger;
2264
+ utils.summary("Loaded commands", {
2265
+ global: this.globalCommands.length,
2266
+ "guild groups": this.guildCommands.size
2267
+ });
2268
+ let slash = 0;
2269
+ let menu = 0;
2270
+ for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
2271
+ else menu++;
2272
+ utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
2273
+ "slash commands": slash,
2274
+ "context menus": menu
2275
+ })]);
2276
+ }
2424
2277
  /** @internal */
2425
2278
  async refresh(shouldRefresh = true) {
2426
2279
  if (!shouldRefresh) {
@@ -2458,25 +2311,27 @@ var CommandRegistry = class {
2458
2311
  const meta = Reflect.getMetadata(CommandMetadataKey, ctor);
2459
2312
  if (!meta) return;
2460
2313
  const comp = new ctor().component;
2461
- const commandType = comp instanceof SlashCommandBuilder ? "slash command" : "context menu command";
2462
- if (meta.scope === "global") {
2463
- this.globalCommands.push(comp);
2464
- this.logger.utils.registration(ctor.name, rel);
2465
- this.logger.utils.item(`Global ${commandType}: ${chalk.bold.cyan(comp.name)}`);
2466
- } else {
2467
- for (const g of meta.guilds) {
2468
- const arr = this.guildCommands.get(g) ?? [];
2469
- arr.push(comp);
2470
- this.guildCommands.set(g, arr);
2471
- }
2472
- this.logger.utils.registration(ctor.name, rel);
2473
- this.logger.utils.item(`Guild ${commandType}: ${chalk.bold.cyan(comp.name)} for ${chalk.magenta.bold(meta.guilds.length)} guild(s)`);
2314
+ const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
2315
+ if (meta.scope === "global") this.globalCommands.push(comp);
2316
+ else for (const g of meta.guilds) {
2317
+ const arr = this.guildCommands.get(g) ?? [];
2318
+ arr.push(comp);
2319
+ this.guildCommands.set(g, arr);
2474
2320
  }
2475
2321
  this.ctorToCommand.set(ctor, {
2476
2322
  name: comp.name,
2477
2323
  scope: meta.scope,
2478
2324
  ...meta.scope === "guild" && { guilds: meta.guilds }
2479
2325
  });
2326
+ if (this.loading) {
2327
+ this.loadedCommands.push({
2328
+ name: comp.name,
2329
+ from: formatFilePath(rel),
2330
+ kind
2331
+ });
2332
+ return;
2333
+ }
2334
+ this.logger.utils.registration(comp.name, rel, `${meta.scope} ${kind}`);
2480
2335
  }
2481
2336
  unregisterCommand(ctor, artifacts) {
2482
2337
  const info = artifacts ?? this.ctorToCommand.get(ctor);
@@ -2503,8 +2358,7 @@ var CommandRegistry = class {
2503
2358
  const deployed = await this.core.bot.client.application?.commands.set(this.globalCommands);
2504
2359
  if (deployed) result.global = deployed;
2505
2360
  const tag = this.globalCommands.length === 1 ? "command" : "commands";
2506
- this.logger.utils.summary("Configured global", { [tag]: this.globalCommands.length });
2507
- this.logger.utils.item(`${this.globalCommands.map((command) => chalk.bold.cyan(command.name)).join(", ")}`);
2361
+ this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
2508
2362
  }
2509
2363
  for (const [guildId, commands] of this.guildCommands.entries()) {
2510
2364
  const guild = this.core.bot.client.guilds.cache.get(guildId);
@@ -2515,8 +2369,7 @@ var CommandRegistry = class {
2515
2369
  const deployed = await guild.commands.set(commands);
2516
2370
  result.guilds.set(guildId, deployed);
2517
2371
  const tag = commands.length === 1 ? "command" : "commands";
2518
- this.logger.utils.summary(`Configured commands for ${chalk.bold.yellow(guild.name)}`, { [tag]: commands.length });
2519
- this.logger.utils.item(`${commands.map((command) => chalk.bold.cyan(command.name)).join(", ")}`);
2372
+ this.logger.utils.block(`Deployed ${commands.length} ${tag} to ${paint.amber.bold(guild.name)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
2520
2373
  }
2521
2374
  this.onDeployed?.(result);
2522
2375
  return result;
@@ -2828,6 +2681,9 @@ var EventDispatcher = class {
2828
2681
  middlewares = [];
2829
2682
  executedOnceHandlers = /* @__PURE__ */ new Set();
2830
2683
  attachedEvents = /* @__PURE__ */ new Set();
2684
+ loading = false;
2685
+ loadedHandlers = [];
2686
+ loadedMiddlewares = [];
2831
2687
  hmrHandler;
2832
2688
  constructor(core) {
2833
2689
  this.core = core;
@@ -2857,20 +2713,29 @@ var EventDispatcher = class {
2857
2713
  this.isInitialized = true;
2858
2714
  const handlersDir = this.core.config.bot.events.path;
2859
2715
  if (!handlersDir) return;
2860
- this.logger.info(chalk.bold(handlersDir));
2861
2716
  const middlewareDir = hasKeys(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
2862
- if (middlewareDir) {
2863
- this.logger.info(`${chalk.bold(middlewareDir)} ${chalk.gray("(middlewares)")}`);
2864
- await this.loadMiddlewares(middlewareDir);
2717
+ this.loading = true;
2718
+ this.loadedHandlers.length = 0;
2719
+ this.loadedMiddlewares.length = 0;
2720
+ try {
2721
+ if (middlewareDir) await this.loadMiddlewares(middlewareDir);
2722
+ await this.loadHandlers(handlersDir);
2723
+ } finally {
2724
+ this.loading = false;
2865
2725
  }
2866
- await this.loadHandlers(handlersDir);
2867
2726
  this.attachToClient();
2868
- this.logger.info(`${chalk.bold.green("Loaded event handlers:")}`);
2869
- const eventList = [`${chalk.magenta.bold(this.middlewares.length)} middlewares`];
2870
- this.eventMap.forEach((handlers, eventName) => {
2871
- eventList.push(`${chalk.magenta.bold(handlers.length)} ${eventName}`);
2727
+ this.reportLoad();
2728
+ }
2729
+ reportLoad() {
2730
+ const { utils } = this.logger;
2731
+ utils.summary("Loaded", {
2732
+ "event handlers": this.loadedHandlers.length,
2733
+ middlewares: this.loadedMiddlewares.length
2872
2734
  });
2873
- this.logger.utils.list(eventList);
2735
+ if (this.loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(this.loadedMiddlewares));
2736
+ const perEvent = {};
2737
+ for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
2738
+ utils.block("Loaded event handlers", [...utils.entries(this.loadedHandlers), ...utils.counts(perEvent)]);
2874
2739
  }
2875
2740
  async loadHandlers(dir) {
2876
2741
  await traverseDirectory(dir, (fullPath, relativePath, imported) => {
@@ -2920,7 +2785,14 @@ var EventDispatcher = class {
2920
2785
  ...metadata.events && { events: metadata.events }
2921
2786
  });
2922
2787
  this.middlewares.sort((a, b) => a.priority - b.priority);
2923
- this.logger.utils.registration(`${middlewareCtor.name} ${chalk.gray(`(${metadata.priority})`)}`, relativePath, "event middleware");
2788
+ if (this.loading) {
2789
+ this.loadedMiddlewares.push({
2790
+ name: `${middlewareCtor.name} (${metadata.priority})`,
2791
+ from: formatFilePath(relativePath)
2792
+ });
2793
+ return;
2794
+ }
2795
+ this.logger.utils.registration(`${middlewareCtor.name} ${paint.mute(`(${metadata.priority})`)}`, relativePath, "event middleware");
2924
2796
  }
2925
2797
  async runMiddlewares(eventName, args) {
2926
2798
  for (const { ctor, events } of this.middlewares) {
@@ -2961,7 +2833,12 @@ var EventDispatcher = class {
2961
2833
  const names = areRoutes(raw) ? raw : typeof raw === "string" ? [raw] : [];
2962
2834
  for (const name of names) register(name, "on");
2963
2835
  }
2964
- this.logger.utils.registration(handlerClass.name, relativePath);
2836
+ const from = formatFilePath(relativePath);
2837
+ if (this.loading) this.loadedHandlers.push({
2838
+ name: handlerClass.name,
2839
+ from
2840
+ });
2841
+ else this.logger.utils.registration(handlerClass.name, from);
2965
2842
  }
2966
2843
  attachToClient() {
2967
2844
  for (const [eventName] of this.eventMap) this.attachListener(eventName);
@@ -2970,12 +2847,12 @@ var EventDispatcher = class {
2970
2847
  if (this.attachedEvents.has(eventName)) return;
2971
2848
  this.attachedEvents.add(eventName);
2972
2849
  const handlerEntries = this.eventMap.get(eventName);
2973
- this.logger.debug(`Attaching ${chalk.bold.green(eventName)} to the client with ${chalk.gray(handlerEntries?.length ?? 0)} handler(s)`);
2850
+ this.logger.debug(`Attaching ${paint.mint.bold(eventName)} to the client with ${paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
2974
2851
  this.core.bot.client.on(eventName, (...args) => {
2975
2852
  this.core.bot.emitSafe("any:event", eventName, ...args);
2976
2853
  (async () => {
2977
2854
  await this.processEvent(eventName, args).catch((err) => {
2978
- this.logger.error(`[${chalk.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
2855
+ this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
2979
2856
  this.core.bot.emit("error:unhandled:event", err);
2980
2857
  });
2981
2858
  })();
@@ -2997,7 +2874,7 @@ var EventDispatcher = class {
2997
2874
  }
2998
2875
  async processHandler(eventName, ctor, args) {
2999
2876
  try {
3000
- this.logger.debug(`Processing ${chalk.bold.green(eventName)} with ${chalk.gray(ctor.name)}`);
2877
+ this.logger.debug(`Processing ${paint.mint.bold(eventName)} with ${paint.mute(ctor.name)}`);
3001
2878
  const handler = new ctor(args, this.core, eventName);
3002
2879
  await runHandlerGates(ctor, eventGateContext(eventName, args, this.core));
3003
2880
  await handler.execute();
@@ -3089,6 +2966,9 @@ var InteractionDispatcher = class {
3089
2966
  autocompleteMap = /* @__PURE__ */ new Map();
3090
2967
  keysToIgnore = /* @__PURE__ */ new Set();
3091
2968
  middlewares = [];
2969
+ loading = false;
2970
+ loadedHandlers = [];
2971
+ loadedMiddlewares = [];
3092
2972
  hmrHandler;
3093
2973
  routeTypes = [
3094
2974
  [InteractionRoutes.Slash, this.slashMap],
@@ -3131,7 +3011,7 @@ var InteractionDispatcher = class {
3131
3011
  * @internal
3132
3012
  */
3133
3013
  warnUnhandledRoutes(commandLeaves) {
3134
- for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${chalk.bold.cyan(route)} has no registered ${chalk.bold("@SlashRoute")} handler.`);
3014
+ for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${paint.sky.bold(route)} has no registered ${chalk.bold("@SlashRoute")} handler.`);
3135
3015
  }
3136
3016
  /**
3137
3017
  * Warns for each context-menu command with no matching handler, which would fall through to UnhandledEvent
@@ -3140,8 +3020,8 @@ var InteractionDispatcher = class {
3140
3020
  * @internal
3141
3021
  */
3142
3022
  warnUnhandledContextMenuRoutes(leaves) {
3143
- for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${chalk.bold.cyan(name)} has no registered ${chalk.bold("@ContextMenuRoute")} handler.`);
3144
- for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${chalk.bold.cyan(name)} has no registered ${chalk.bold("@ContextMenuRoute")} handler.`);
3023
+ for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${paint.sky.bold(name)} has no registered ${chalk.bold("@ContextMenuRoute")} handler.`);
3024
+ for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${paint.sky.bold(name)} has no registered ${chalk.bold("@ContextMenuRoute")} handler.`);
3145
3025
  }
3146
3026
  getArtifacts(handlerClass) {
3147
3027
  const artifacts = [];
@@ -3159,29 +3039,39 @@ var InteractionDispatcher = class {
3159
3039
  this.isInitialized = true;
3160
3040
  const handlersDir = this.core.config.bot.interactions.path;
3161
3041
  if (!handlersDir) return;
3162
- this.logger.info(chalk.bold(handlersDir));
3163
3042
  const middlewareDir = hasKeys(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
3164
- if (middlewareDir) {
3165
- this.logger.info(`${chalk.bold(middlewareDir)} ${chalk.gray("(middlewares)")}`);
3166
- await this.loadMiddlewares(middlewareDir);
3043
+ this.loading = true;
3044
+ this.loadedHandlers.length = 0;
3045
+ this.loadedMiddlewares.length = 0;
3046
+ try {
3047
+ if (middlewareDir) await this.loadMiddlewares(middlewareDir);
3048
+ await this.loadHandlers(handlersDir);
3049
+ } finally {
3050
+ this.loading = false;
3167
3051
  }
3168
- await this.loadHandlers(handlersDir);
3169
3052
  this.attachToClient();
3170
- this.logger.info(`${chalk.bold.green("Loaded interaction handlers:")}`);
3171
- this.logger.utils.list([
3172
- `${chalk.magenta.bold(this.middlewares.length)} middlewares`,
3173
- `${chalk.magenta.bold(this.slashMap.size)} slash commands`,
3174
- `${chalk.magenta.bold(this.buttonMap.size)} buttons`,
3175
- `${chalk.magenta.bold(this.modalMap.size)} modals`,
3176
- `${chalk.magenta.bold(this.stringSelectMap.size)} string selects`,
3177
- `${chalk.magenta.bold(this.userSelectMap.size)} user selects`,
3178
- `${chalk.magenta.bold(this.roleSelectMap.size)} role selects`,
3179
- `${chalk.magenta.bold(this.channelSelectMap.size)} channel selects`,
3180
- `${chalk.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`,
3181
- `${chalk.magenta.bold(this.messageContextMenuMap.size)} message context menus`,
3182
- `${chalk.magenta.bold(this.userContextMenuMap.size)} user context menus`,
3183
- `${chalk.magenta.bold(this.autocompleteMap.size)} autocomplete`
3184
- ]);
3053
+ this.reportLoad();
3054
+ }
3055
+ reportLoad() {
3056
+ const { utils } = this.logger;
3057
+ utils.summary("Loaded", {
3058
+ "interaction handlers": this.loadedHandlers.length,
3059
+ middlewares: this.loadedMiddlewares.length
3060
+ });
3061
+ if (this.loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(this.loadedMiddlewares));
3062
+ utils.block("Loaded interaction handlers", [...utils.entries(this.loadedHandlers), ...utils.counts({
3063
+ slash: this.slashMap.size,
3064
+ buttons: this.buttonMap.size,
3065
+ modals: this.modalMap.size,
3066
+ "string selects": this.stringSelectMap.size,
3067
+ "user selects": this.userSelectMap.size,
3068
+ "role selects": this.roleSelectMap.size,
3069
+ "channel selects": this.channelSelectMap.size,
3070
+ "mentionable selects": this.mentionableSelectMap.size,
3071
+ "message menus": this.messageContextMenuMap.size,
3072
+ "user menus": this.userContextMenuMap.size,
3073
+ autocomplete: this.autocompleteMap.size
3074
+ })]);
3185
3075
  }
3186
3076
  async loadHandlers(dir) {
3187
3077
  await traverseDirectory(dir, (fullPath, relativePath, imported) => {
@@ -3211,7 +3101,14 @@ var InteractionDispatcher = class {
3211
3101
  priority: metadata.priority
3212
3102
  });
3213
3103
  this.middlewares.sort((a, b) => a.priority - b.priority);
3214
- this.logger.utils.registration(`${middlewareCtor.name} ${chalk.gray(`(${metadata.priority})`)}`, relativePath, "middleware");
3104
+ if (this.loading) {
3105
+ this.loadedMiddlewares.push({
3106
+ name: `${middlewareCtor.name} (${metadata.priority})`,
3107
+ from: formatFilePath(relativePath)
3108
+ });
3109
+ return;
3110
+ }
3111
+ this.logger.utils.registration(`${middlewareCtor.name} ${paint.mute(`(${metadata.priority})`)}`, relativePath, "middleware");
3215
3112
  }
3216
3113
  isHandlerClass(obj) {
3217
3114
  if (typeof obj !== "function") return false;
@@ -3235,9 +3132,15 @@ var InteractionDispatcher = class {
3235
3132
  ]);
3236
3133
  writes.push([map, route]);
3237
3134
  }
3238
- this.logger.utils.registration(handlerClass.name, formatFilePath(relativePath));
3239
3135
  }
3136
+ if (writes.length === 0) return;
3240
3137
  for (const [map, route] of writes) map.set(route, handlerClass);
3138
+ const from = formatFilePath(relativePath);
3139
+ if (this.loading) this.loadedHandlers.push({
3140
+ name: handlerClass.name,
3141
+ from
3142
+ });
3143
+ else this.logger.utils.registration(handlerClass.name, from);
3241
3144
  }
3242
3145
  /** @internal For use in dev mode */
3243
3146
  async onHmr(event) {
@@ -3265,7 +3168,7 @@ var InteractionDispatcher = class {
3265
3168
  this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
3266
3169
  this.core.bot.emitSafe("any:interaction", interaction);
3267
3170
  this.handleInteraction(interaction).catch((err) => {
3268
- this.logger.error(`[${chalk.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
3171
+ this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
3269
3172
  this.core.bot.emit("error:unhandled:interaction", err);
3270
3173
  });
3271
3174
  });
@@ -3282,10 +3185,10 @@ var InteractionDispatcher = class {
3282
3185
  if (!interaction.isAutocomplete()) for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
3283
3186
  let HandlerCtor = getHandler(key);
3284
3187
  if (!HandlerCtor) {
3285
- this.logger.warn(`No handler found for key ${chalk.bold.cyan(key)}. Falling back to UnhandledEvent.`);
3188
+ this.logger.warn(`No handler found for key ${paint.sky.bold(key)}. Falling back to UnhandledEvent.`);
3286
3189
  HandlerCtor = UnhandledEvent;
3287
3190
  }
3288
- this.logger.debug(`Processing ${chalk.bold.green(key)} with ${chalk.gray(HandlerCtor.name)}`);
3191
+ this.logger.debug(`Processing ${paint.mint.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
3289
3192
  const dispatch = new DispatchContext(routeIdOf(HandlerCtor));
3290
3193
  const handler = new HandlerCtor(interaction, this.core, dispatch);
3291
3194
  if (!interaction.isAutocomplete()) await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core));
@@ -3368,7 +3271,7 @@ var InteractionDispatcher = class {
3368
3271
  async handleAutocomplete(interaction) {
3369
3272
  const route = slashRouteOf(interaction);
3370
3273
  if (!this.autocompleteMap.has(route)) {
3371
- this.logger.warn(`No autocomplete handler for ${chalk.bold.cyan(route)}.`);
3274
+ this.logger.warn(`No autocomplete handler for ${paint.sky.bold(route)}.`);
3372
3275
  await interaction.respond([]);
3373
3276
  return;
3374
3277
  }
@@ -3487,46 +3390,6 @@ var Bot = class extends Plugin {
3487
3390
  };
3488
3391
  __decorate([Envapt("DISCORD_BOT_TOKEN", { converter: (raw) => validateDiscordToken(raw) }), __decorateMetadata("design:type", String)], Bot.prototype, "botToken", void 0);
3489
3392
 
3490
- //#endregion
3491
- //#region src/hmr/HmrManager.ts
3492
- var HmrManager = class {
3493
- logger = new Logger("HMR", { channel: "hmr" });
3494
- listeners = /* @__PURE__ */ new Set();
3495
- constructor() {}
3496
- /** @internal */
3497
- init() {
3498
- if (!import.meta.hot) return;
3499
- setDevChannel(wrapHot(import.meta.hot));
3500
- if (Envapter.isDevelopment || Envapter.isTest) {
3501
- this.logger.info("Enabled");
3502
- getDevChannel()?.on("seedcord:hmr", (payload) => {
3503
- const affected = payload.affectedModules?.length ?? 0;
3504
- this.logger.info(`${chalk.bold("1")} module changed, ${chalk.bold(affected)} affected modules`);
3505
- if (payload.affectedModules) this.logger.utils.list(payload.affectedModules.map((mod) => formatFilePath(mod)), "Affected modules: ");
3506
- this.handleUpdate(payload);
3507
- });
3508
- }
3509
- }
3510
- /** @internal */
3511
- register(listener) {
3512
- this.listeners.add(listener);
3513
- }
3514
- /** @internal */
3515
- unregister(listener) {
3516
- this.listeners.delete(listener);
3517
- }
3518
- async handleUpdate(event) {
3519
- const promises = [...this.listeners].map(async (listener) => {
3520
- try {
3521
- await listener.onHmr(event);
3522
- } catch (error) {
3523
- this.logger.error(`Error handling HMR update in ${chalk.bold(listener.name)}: `, error);
3524
- }
3525
- });
3526
- await Promise.all(promises);
3527
- }
3528
- };
3529
-
3530
3393
  //#endregion
3531
3394
  //#region src/subscribers/bases/webhookHelpers.ts
3532
3395
  const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
@@ -3817,7 +3680,7 @@ var Bus = class extends Plugin {
3817
3680
  //#endregion
3818
3681
  //#region src/version.ts
3819
3682
  /** Package version */
3820
- const version = "0.1.0-next.1";
3683
+ const version = "0.1.0-next.2";
3821
3684
 
3822
3685
  //#endregion
3823
3686
  //#region src/Seedcord.ts
@@ -3836,6 +3699,8 @@ var Seedcord = class Seedcord extends Pluggable {
3836
3699
  /** @internal */
3837
3700
  version = version;
3838
3701
  static isInstantiated = false;
3702
+ healthCheck;
3703
+ hmrManager;
3839
3704
  /** @see {@link CoordinatedShutdown} */
3840
3705
  shutdown;
3841
3706
  /** @see {@link CoordinatedStartup} */
@@ -3846,10 +3711,6 @@ var Seedcord = class Seedcord extends Pluggable {
3846
3711
  bot;
3847
3712
  /** @see {@link IRateLimiter} */
3848
3713
  rateLimiter;
3849
- /** @see {@link HealthCheck} */
3850
- healthCheck;
3851
- /** @see {@link HmrManager} */
3852
- hmrManager;
3853
3714
  /**
3854
3715
  * Creates a new Seedcord instance
3855
3716
  *
@@ -3863,6 +3724,7 @@ var Seedcord = class Seedcord extends Pluggable {
3863
3724
  const startup = new CoordinatedStartup();
3864
3725
  super(shutdown, startup);
3865
3726
  this.config = config;
3727
+ installNodeDefaults(config.logger);
3866
3728
  setBotColor(config.botColor);
3867
3729
  this.shutdown = shutdown;
3868
3730
  this.startup = startup;
@@ -3874,14 +3736,14 @@ var Seedcord = class Seedcord extends Pluggable {
3874
3736
  this.healthCheck = new HealthCheck(this.shutdown, config.healthCheck);
3875
3737
  this.registerStartupTasks();
3876
3738
  }
3877
- /** @internal */
3739
+ /** The bot's discord username, populated after login. */
3740
+ get username() {
3741
+ return this.bot.client.user?.username;
3742
+ }
3878
3743
  static reset() {
3879
3744
  Seedcord.isInstantiated = false;
3745
+ LoggerChannelRegistry.instance.reset();
3880
3746
  }
3881
- /**
3882
- * Registers default startup tasks
3883
- * @internal
3884
- */
3885
3747
  registerStartupTasks() {
3886
3748
  if (Envapter.isDevelopment || Envapter.isTest) this.registerHmrAwareModules();
3887
3749
  this.startup.addTask(StartupPhase.Configuration, "Bus Initialization", async () => {
@@ -3920,5 +3782,5 @@ var Seedcord = class Seedcord extends Pluggable {
3920
3782
  };
3921
3783
 
3922
3784
  //#endregion
3923
- export { ArraySource, AutocompleteHandler, AutocompleteRoute, ButtonHandler, ButtonRoute, CommandMentions, ContextMenuHandler, ContextMenuRoute, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, HmrModuleHandler, IgnoreBots, InteractionHandler, InteractionMiddleware, Middleware, MiddlewareType, ModalHandler, ModalRoute, Nsfw, Paginator, Pluggable, Plugin, RegisterCommand, RegisterEvent, ReplySender, RequireBotPermissions, RequirePermissions, RequireRole, Seedcord, SelectMenuHandler, SelectMenuKind, SelectMenuRoute, SlashHandler, SlashRoute, Subscribe, Subscriber, WebhookLog, checkBotPermissions, checkPermissions, fetchGuildMember, fetchManyGuildMembers, fetchManyUsers, fetchRole, fetchText, fetchUser, getBotRole, getConfirmation, hasPermsToAssign, updateMemberRoles, version };
3785
+ export { ArraySource, AutocompleteHandler, AutocompleteRoute, ButtonHandler, ButtonRoute, CommandMentions, ContextMenuHandler, ContextMenuRoute, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, IgnoreBots, InteractionHandler, InteractionMiddleware, Middleware, MiddlewareType, ModalHandler, ModalRoute, Nsfw, Paginator, Pluggable, Plugin, RegisterCommand, RegisterEvent, ReplySender, RequireBotPermissions, RequirePermissions, RequireRole, Seedcord, SelectMenuHandler, SelectMenuKind, SelectMenuRoute, SlashHandler, SlashRoute, Subscribe, Subscriber, WebhookLog, checkBotPermissions, checkPermissions, fetchGuildMember, fetchManyGuildMembers, fetchManyUsers, fetchRole, fetchText, fetchUser, getBotRole, getConfirmation, hasPermsToAssign, updateMemberRoles, version };
3924
3786
  //# sourceMappingURL=index.mjs.map