@seedcord/core 0.1.0-next.6 → 0.1.0-next.8

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.
Files changed (64) hide show
  1. package/dist/HmrModuleHandler-3sNgKHdf.mjs +163 -0
  2. package/dist/HmrModuleHandler-3sNgKHdf.mjs.map +1 -0
  3. package/dist/HmrModuleHandler-CTBng7wV.cjs +170 -0
  4. package/dist/HmrModuleHandler-CTBng7wV.cjs.map +1 -0
  5. package/dist/Plugin-BZit5HUI.d.mts +555 -0
  6. package/dist/{Plugin-C2-VHrsX.cjs → Plugin-BlkhHNRm.cjs} +15 -34
  7. package/dist/Plugin-BlkhHNRm.cjs.map +1 -0
  8. package/dist/{Plugin-PcdPjAzc.mjs → Plugin-CDAV7P9l.mjs} +16 -29
  9. package/dist/Plugin-CDAV7P9l.mjs.map +1 -0
  10. package/dist/basic-1hebYVcN.d.mts +22 -0
  11. package/dist/contextMenuLeaves-f2rbyo83.d.mts +19 -0
  12. package/dist/{permissions-DSW_EZfN.cjs → cursor-BMkFAwO2.cjs} +308 -300
  13. package/dist/cursor-BMkFAwO2.cjs.map +1 -0
  14. package/dist/{permissions-RVCBhL2n.mjs → cursor-BvSH8tgw.mjs} +249 -247
  15. package/dist/cursor-BvSH8tgw.mjs.map +1 -0
  16. package/dist/{AutocompleteOptions-eKhIe6Qt.d.mts → cursor-D6nBjwaE.d.mts} +207 -116
  17. package/dist/hmr.index.cjs +2 -164
  18. package/dist/hmr.index.mjs +2 -162
  19. package/dist/index-Cr4-VV4u.d.mts +142 -0
  20. package/dist/index.cjs +362 -63
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.mts +239 -38
  23. package/dist/index.mjs +313 -26
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/internal.index.cjs +278 -83
  26. package/dist/internal.index.cjs.map +1 -1
  27. package/dist/internal.index.d.mts +111 -42
  28. package/dist/internal.index.mjs +223 -43
  29. package/dist/internal.index.mjs.map +1 -1
  30. package/dist/node-internal.index.cjs +314 -76
  31. package/dist/node-internal.index.cjs.map +1 -1
  32. package/dist/node-internal.index.d.mts +123 -2164
  33. package/dist/node-internal.index.mjs +313 -77
  34. package/dist/node-internal.index.mjs.map +1 -1
  35. package/dist/{phases-BfpAG6w3.mjs → phases-BauKJOTT.mjs} +2 -2
  36. package/dist/phases-BauKJOTT.mjs.map +1 -0
  37. package/dist/{phases-C_BGNyVo.cjs → phases-DcuDHoQl.cjs} +2 -2
  38. package/dist/phases-DcuDHoQl.cjs.map +1 -0
  39. package/dist/plugin.index.cjs +1 -1
  40. package/dist/plugin.index.d.mts +2 -2
  41. package/dist/plugin.index.mjs +1 -1
  42. package/dist/publishDefault-CVlwYUS5.cjs +382 -0
  43. package/dist/publishDefault-CVlwYUS5.cjs.map +1 -0
  44. package/dist/publishDefault-CldnxcT2.mjs +287 -0
  45. package/dist/publishDefault-CldnxcT2.mjs.map +1 -0
  46. package/dist/slashRouteLeaves-BANDtPFm.mjs +23 -0
  47. package/dist/slashRouteLeaves-BANDtPFm.mjs.map +1 -0
  48. package/dist/slashRouteLeaves-nfxOAM7Q.cjs +28 -0
  49. package/dist/slashRouteLeaves-nfxOAM7Q.cjs.map +1 -0
  50. package/dist/subscribers-BSJoyKNN.cjs +517 -0
  51. package/dist/subscribers-BSJoyKNN.cjs.map +1 -0
  52. package/dist/subscribers-DuxdM2cS.mjs +480 -0
  53. package/dist/subscribers-DuxdM2cS.mjs.map +1 -0
  54. package/package.json +7 -6
  55. package/dist/Plugin-C2-VHrsX.cjs.map +0 -1
  56. package/dist/Plugin-CNyRKDHT.d.mts +0 -226
  57. package/dist/Plugin-PcdPjAzc.mjs.map +0 -1
  58. package/dist/hmr.index.cjs.map +0 -1
  59. package/dist/hmr.index.mjs.map +0 -1
  60. package/dist/is-never-Ddaba1Su.d.mts +0 -58
  61. package/dist/permissions-DSW_EZfN.cjs.map +0 -1
  62. package/dist/permissions-RVCBhL2n.mjs.map +0 -1
  63. package/dist/phases-BfpAG6w3.mjs.map +0 -1
  64. package/dist/phases-C_BGNyVo.cjs.map +0 -1
@@ -1,11 +1,21 @@
1
- import { n as StartupPhase, t as ShutdownPhase } from "./phases-BfpAG6w3.mjs";
2
- import { n as resolvedLifecycleSpecOf, r as finalizePluginContext } from "./Plugin-PcdPjAzc.mjs";
1
+ import { g as contextMenuLeaves, i as BuilderComponent, m as SubscribeMetadataKey, s as CommandMetadataKey } from "./publishDefault-CldnxcT2.mjs";
2
+ import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
3
+ import { n as registrationFor, o as Subscriber } from "./subscribers-DuxdM2cS.mjs";
4
+ import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
5
+ import { n as resolvedLifecycleSpecOf } from "./Plugin-CDAV7P9l.mjs";
6
+ import { t as slashRouteLeaves } from "./slashRouteLeaves-BANDtPFm.mjs";
7
+ import { t as HmrModuleHandler } from "./HmrModuleHandler-3sNgKHdf.mjs";
8
+ import { SlashCommandBuilder } from "@discordjs/builders";
9
+ import { FRAMEWORK_CHANNELS, Logger, paint } from "@seedcord/logger";
10
+ import { Routes } from "discord-api-types/v10";
3
11
  import { SeedcordErrorCode } from "@seedcord/errors";
4
12
  import { SeedcordError } from "@seedcord/errors/internal";
5
- import { Logger, paint } from "@seedcord/logger";
13
+ import { formatFilePath } from "@seedcord/utils";
6
14
  import chalk from "chalk";
7
- import { TypedEventEmitter } from "@seedcord/event-emitter";
15
+ import { Envapter } from "envapt";
16
+ import { resolve } from "node:path";
8
17
  import { createServer } from "node:http";
18
+ import { traverseDirectory } from "@seedcord/utils/node";
9
19
 
10
20
  //#region src/node/Lifecycle/withTimeout.ts
11
21
  async function raceTimer(work, timeoutMs, onTimeout) {
@@ -36,16 +46,15 @@ function settleWithin(work, timeoutMs) {
36
46
  *
37
47
  * @internal
38
48
  */
39
- var CoordinatedLifecycle = class extends TypedEventEmitter {
49
+ var CoordinatedLifecycle = class {
40
50
  phaseOrder;
41
51
  phaseEnum;
42
52
  logger;
43
53
  tasksMap = /* @__PURE__ */ new Map();
44
54
  constructor(loggerName, phaseOrder, phaseEnum) {
45
- super();
46
55
  this.phaseOrder = phaseOrder;
47
56
  this.phaseEnum = phaseEnum;
48
- this.logger = new Logger(loggerName);
57
+ this.logger = new Logger(loggerName, { channel: "lifecycle" });
49
58
  this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
50
59
  }
51
60
  /**
@@ -92,11 +101,9 @@ var CoordinatedLifecycle = class extends TypedEventEmitter {
92
101
  return;
93
102
  }
94
103
  this.logger.info(`${chalk.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk.bold.magenta(this.phaseEnum[phase])} with ${chalk.bold.cyan(tasks.length)} tasks`);
95
- this.emitPhase(phase, "start");
96
104
  const failures = (await this.executeTasksInPhase(phase, tasks)).filter((r) => r.status === "rejected").length;
97
105
  if (failures > 0) throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [this.phaseEnum[phase], failures]);
98
106
  this.logger.info(`Phase ${chalk.bold.magenta(this.phaseEnum[phase])} ${chalk.bold.green("completed successfully")}`);
99
- this.emitPhase(phase, "complete");
100
107
  }
101
108
  async runTaskWithTimeout(phase, task) {
102
109
  this.logger.info(`${chalk.italic("Starting")} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
@@ -108,12 +115,6 @@ var CoordinatedLifecycle = class extends TypedEventEmitter {
108
115
  throw error;
109
116
  }
110
117
  }
111
- emitPhase(phase, action) {
112
- this.emitSafeRaw(`phase:${phase}:${action}`);
113
- }
114
- onListenerError(error, event) {
115
- this.logger.error(`listener for ${String(event)} threw`, error);
116
- }
117
118
  };
118
119
 
119
120
  //#endregion
@@ -176,11 +177,18 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
176
177
  this.onSigInt = null;
177
178
  }
178
179
  }
179
- /** @internal run() awaits this so a shutdown during startup waits for startup to register its dispose tasks */
180
+ /** @internal run() awaits this so boot finishes registering its dispose tasks first */
180
181
  gateOnStartup(settled) {
181
182
  this.startupGate = settled;
182
183
  }
183
- /** Adds a shutdown-phase task. @param timeoutMs - Task timeout in ms. {@default 5000} */
184
+ /**
185
+ * Adds a shutdown-phase task.
186
+ *
187
+ * @param phase - The shutdown phase to run the task in.
188
+ * @param taskName - A descriptive name for the task.
189
+ * @param task - The async function to run.
190
+ * @param timeoutMs - Task timeout in ms. {@default `5000` }
191
+ */
184
192
  addTask(phase, taskName, task, timeoutMs = 5e3) {
185
193
  super.addTask(phase, taskName, task, timeoutMs);
186
194
  }
@@ -192,8 +200,8 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
192
200
  * Runs registered tasks across shutdown phases in `ShutdownPhase` order.
193
201
  * Tasks within each phase run in parallel.
194
202
  *
195
- * @param exitCode - Process exit code. {@default `0`}
196
- * @param exitProcess - Whether to exit the process after shutdown. {@default `true`}
203
+ * @param exitCode - Process exit code. {@default `0` }
204
+ * @param exitProcess - Whether to exit the process after shutdown. {@default `true` }
197
205
  * @example
198
206
  * ```typescript
199
207
  * shutdown.addTask(ShutdownPhase.Disconnect, 'database', () => db.disconnect(), 5000);
@@ -209,7 +217,6 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
209
217
  this.isShuttingDown = true;
210
218
  this.exitCode = exitCode;
211
219
  this.logger.info(`${chalk.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk.bold.cyan(exitCode)}`);
212
- this.emitSafe("shutdown:start");
213
220
  try {
214
221
  if (this.startupGate) await this.startupGate;
215
222
  const failures = [];
@@ -218,11 +225,8 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
218
225
  } catch (error) {
219
226
  failures.push(error);
220
227
  }
221
- if (failures.length > 0) this.emitFailures(failures);
222
- else {
223
- this.logger.info(`${chalk.bold.green("Coordinated shutdown completed")} successfully`);
224
- this.emitSafe("shutdown:complete");
225
- }
228
+ if (failures.length > 0) this.logger.error(`${chalk.bold.red("Coordinated shutdown failed")}`, ...failures);
229
+ else this.logger.info(`${chalk.bold.green("Coordinated shutdown completed")} successfully`);
226
230
  } finally {
227
231
  this.hasShutdown = true;
228
232
  if (exitProcess) {
@@ -236,11 +240,6 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
236
240
  }
237
241
  }
238
242
  }
239
- emitFailures(failures) {
240
- this.logger.error(`${chalk.bold.red("Coordinated shutdown failed")}`);
241
- const error = failures.length === 1 ? failures[0] : new AggregateError(failures, "shutdown phase failures");
242
- this.emitSafe("shutdown:error", error);
243
- }
244
243
  };
245
244
 
246
245
  //#endregion
@@ -259,7 +258,14 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
259
258
  constructor() {
260
259
  super("Startup", PHASE_ORDER, StartupPhase);
261
260
  }
262
- /** Adds a startup-phase task. @param timeoutMs - Task timeout in ms. {@default 10000} */
261
+ /**
262
+ * Adds a startup-phase task.
263
+ *
264
+ * @param phase - The startup phase to run the task in.
265
+ * @param taskName - A descriptive name for the task.
266
+ * @param task - The async function to run.
267
+ * @param timeoutMs - Task timeout in ms. {@default `10000` }
268
+ */
263
269
  addTask(phase, taskName, task, timeoutMs = 1e4) {
264
270
  super.addTask(phase, taskName, task, timeoutMs);
265
271
  }
@@ -296,7 +302,6 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
296
302
  }
297
303
  this.isStartingUp = true;
298
304
  this.logger.info(`${chalk.bold.green("Starting")} coordinated startup sequence`);
299
- this.emitSafe("startup:start");
300
305
  try {
301
306
  for (const phase of PHASE_ORDER) {
302
307
  if (!this.isStartingUp) {
@@ -307,14 +312,12 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
307
312
  }
308
313
  this.hasStarted = true;
309
314
  this.logger.info(`${chalk.bold.green("Coordinated startup completed")} successfully`);
310
- this.emitSafe("startup:complete");
311
315
  } catch (error) {
312
316
  if (!this.isStartingUp) {
313
317
  this.logger.warn("Startup sequence aborted during error handling");
314
318
  return;
315
319
  }
316
320
  this.logger.error(`${chalk.bold.red("Coordinated startup failed")}`);
317
- this.emitSafe("startup:error", error);
318
321
  throw error;
319
322
  } finally {
320
323
  this.isStartingUp = false;
@@ -358,7 +361,7 @@ const DEFAULT_HEALTH_CHECK_PATH = "/health";
358
361
  */
359
362
  var HealthCheck = class HealthCheck {
360
363
  /** @internal */
361
- logger = new Logger("HealthCheck");
364
+ logger = new Logger("HealthCheck", { channel: "health" });
362
365
  port;
363
366
  path;
364
367
  host;
@@ -403,7 +406,7 @@ var HealthCheck = class HealthCheck {
403
406
  server.removeListener("error", onListenError);
404
407
  server.on("error", (err) => this.logger.error("Health check server error", err));
405
408
  const address = this.host ?? "localhost";
406
- this.logger.info(`${paint.mint.bold("✓")} Health check server listening on ${paint.sky(`http://${address}:${this.port}${this.path}`)}`);
409
+ this.logger.info(`${paint.mint.bold("✔︎")} Health check server listening on ${paint.sky(`http://${address}:${this.port}${this.path}`)}`);
407
410
  resolve();
408
411
  });
409
412
  if (this.host) {
@@ -437,19 +440,22 @@ var HealthCheck = class HealthCheck {
437
440
 
438
441
  //#endregion
439
442
  //#region src/node/Pluggable.ts
443
+ const RESERVED_KEYS = new Set(FRAMEWORK_CHANNELS);
440
444
  /**
441
- * Base class for objects that can have plugins attached.
445
+ * Base class for a plugin host, a transport `Seedcord` class.
442
446
  *
443
447
  * Plugins are attached during configuration and initialized during startup, sequentially in attach
444
- * order within each phase. Not constructed directly, the host is a transport `Seedcord` class.
448
+ * order within each phase.
445
449
  */
446
- var Pluggable = class Pluggable extends TypedEventEmitter {
447
- isInitialized = false;
448
- startFailed = false;
450
+ var Pluggable = class Pluggable {
451
+ /** @see {@link CoordinatedShutdown} */
449
452
  shutdown;
453
+ /** @see {@link CoordinatedStartup} */
450
454
  startup;
455
+ isInitialized = false;
456
+ startFailed = false;
451
457
  plugins = [];
452
- pluginLogger = new Logger("Plugins");
458
+ pluginLogger = new Logger("Plugins", { channel: "plugins" });
453
459
  attachments = [];
454
460
  completedInits = /* @__PURE__ */ new Set();
455
461
  disposePhases = /* @__PURE__ */ new Set();
@@ -461,7 +467,6 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
461
467
  shutdown.removeSignalHandlers();
462
468
  throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
463
469
  }
464
- super();
465
470
  Pluggable.isInstantiated = true;
466
471
  Pluggable.liveShutdown = shutdown;
467
472
  this.shutdown = shutdown;
@@ -485,13 +490,11 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
485
490
  this.isInitialized = true;
486
491
  return this;
487
492
  }
488
- pluginCapabilities() {
489
- return {};
493
+ /** @internal codegen reads these to emit the `Core` augmentation */
494
+ get pluginKeys() {
495
+ return this.attachments.map((attachment) => attachment.key);
490
496
  }
491
- pluginStore() {
492
- return this.config.store ?? this.rateLimiter;
493
- }
494
- /** @internal releases the signal handlers and clears the singleton so the next host can construct */
497
+ /** @internal so the next host can construct */
495
498
  static reset() {
496
499
  Pluggable.liveShutdown?.removeSignalHandlers();
497
500
  Pluggable.liveShutdown = void 0;
@@ -501,8 +504,12 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
501
504
  * Attaches a plugin to this instance.
502
505
  *
503
506
  * The plugin initializes during startup, after every plugin attached before it, and becomes
504
- * available as a property on `core`. Augment the transport's `Core` interface with the plugin
505
- * type for intellisense.
507
+ * available as a property on `core`. `seedcord codegen` writes the `Core` augmentation that
508
+ * types it.
509
+ *
510
+ * A plugin declaring a `transport` or `runtime` this host does not run is a compile error on
511
+ * this call. An edge host takes no plugins at all. A plugin constructor narrowing its first
512
+ * parameter past `CoreBase` is a compile error here too. Read the transport `Core` off `this.core`.
506
513
  *
507
514
  * @typeParam Key - The property name for accessing the plugin
508
515
  * @typeParam Ctor - The plugin constructor type
@@ -510,38 +517,25 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
510
517
  * @param Plugin - Plugin constructor class
511
518
  * @param args - Additional arguments to pass to the plugin constructor
512
519
  * @returns This instance with the plugin attached as a typed property
513
- * @throws A **SeedcordError** When called after initialization or if key already exists
520
+ * @throws A **SeedcordError** When called after initialization or if key already exists or is reserved
514
521
  * @example
515
522
  * ```typescript
516
- * seedcord.attach('db', Mongo, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
523
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
517
524
  * ```
518
525
  */
519
526
  attach(key, Plugin, ...args) {
520
527
  if (this.isInitialized) throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
528
+ if (RESERVED_KEYS.has(key)) throw new SeedcordError(SeedcordErrorCode.CorePluginReservedChannel, [key]);
521
529
  if (key in this) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);
522
530
  const instance = new Plugin(this, ...args);
531
+ instance.logger.setChannel(key);
523
532
  this.plugins.push(instance);
524
533
  this.attachments.push({
525
534
  key,
526
535
  instance
527
536
  });
528
- this.finalizeContext(key, instance);
529
537
  return Object.assign(this, { [key]: instance });
530
538
  }
531
- finalizeContext(key, instance) {
532
- const caps = this.pluginCapabilities();
533
- const readToken = () => this.pluginCapabilities().token;
534
- finalizePluginContext(instance, {
535
- logger: new Logger(key),
536
- config: this.config,
537
- store: this.pluginStore(),
538
- client: caps.client,
539
- get token() {
540
- return readToken();
541
- },
542
- rest: caps.rest
543
- });
544
- }
545
539
  registerPluginTasks() {
546
540
  if (this.pluginTasksRegistered) return;
547
541
  this.pluginTasksRegistered = true;
@@ -555,7 +549,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
555
549
  for (const [phase, group] of groups) {
556
550
  if (phase === 3) continue;
557
551
  const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);
558
- this.startup.addTask(phase, "Plugins:init", () => this.runInits(group), budget);
552
+ this.startup.addTask(phase, "plugins-init", () => this.runInits(group), budget);
559
553
  }
560
554
  this.registerReadyTask(groups.get(3) ?? []);
561
555
  }
@@ -564,7 +558,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
564
558
  const { key, instance } = attachment;
565
559
  const spec = resolvedLifecycleSpecOf(instance);
566
560
  instance.logger.utils.initialization(key, "start");
567
- await withTimeout(`Plugin:${key}`, () => instance.init(), spec.init.timeout);
561
+ await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);
568
562
  instance.logger.utils.initialization(key, "end");
569
563
  this.completedInits.add(attachment);
570
564
  if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);
@@ -582,7 +576,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
582
576
  }, []);
583
577
  if (readyInits.length === 0 && steps.length === 0) return;
584
578
  const budget = readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) + steps.reduce((sum, step) => sum + step.timeout, 0);
585
- this.startup.addTask(3, "Plugins", async () => {
579
+ this.startup.addTask(3, "plugins-ready", async () => {
586
580
  await this.runInits(readyInits);
587
581
  for (const step of steps) await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);
588
582
  }, budget);
@@ -594,7 +588,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
594
588
  const spec = resolvedLifecycleSpecOf(a.instance);
595
589
  return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;
596
590
  }, 0);
597
- this.shutdown.addTask(phase, "Plugins:dispose", () => this.runDisposals(phase), budget);
591
+ this.shutdown.addTask(phase, "plugins-dispose", () => this.runDisposals(phase), budget);
598
592
  }
599
593
  async runDisposals(phase) {
600
594
  const failures = [];
@@ -621,16 +615,258 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
621
615
  await this.disposeCompleted(void 0, (caught) => this.pluginLogger.warn("rollback dispose failed", caught));
622
616
  this.completedInits.clear();
623
617
  }
618
+ };
619
+
620
+ //#endregion
621
+ //#region src/node/subscribers/SubscriberLoader.ts
622
+ /**
623
+ * Loads subscribers off the filesystem into a {@link Bus} and stores their hot-reload wiring. Node
624
+ * only, a bundled isolate registers from the manifest instead.
625
+ *
626
+ * @internal
627
+ */
628
+ var SubscriberLoader = class {
629
+ bus;
630
+ directory;
631
+ isInitialized = false;
632
+ hmrHandler;
633
+ constructor(bus, directory) {
634
+ this.bus = bus;
635
+ this.directory = directory;
636
+ if (!this.directory) return;
637
+ if (!Envapter.isDevelopment && !Envapter.isTest) return;
638
+ this.hmrHandler = new HmrModuleHandler({
639
+ handlersDir: this.directory,
640
+ isHandler: this.isSubscriber.bind(this),
641
+ registerHandler: this.registerSubscriber.bind(this),
642
+ unregisterHandler: this.unregisterSubscriber.bind(this),
643
+ getArtifacts: this.getArtifacts.bind(this),
644
+ logger: this.bus.logger
645
+ });
646
+ }
647
+ /** @internal */
648
+ async init() {
649
+ if (this.isInitialized) return;
650
+ this.isInitialized = true;
651
+ this.bus.registerDefaults();
652
+ const { directory } = this;
653
+ if (directory) {
654
+ this.bus.logger.info(chalk.bold(directory));
655
+ await this.load(directory);
656
+ this.bus.logger.utils.list([`${chalk.bold.magenta(this.bus.registeredCount)} subscribers`], chalk.bold.green("Loaded"));
657
+ }
658
+ if (!Envapter.isTest) await this.bus.verifyWebhooks();
659
+ }
660
+ /** @internal */
661
+ async onHmr(event) {
662
+ if (this.hmrHandler) await this.hmrHandler.handle(event);
663
+ }
664
+ async load(dir) {
665
+ await traverseDirectory(dir, (fullPath, relativePath, imported) => {
666
+ for (const exportName of Object.keys(imported)) {
667
+ const value = imported[exportName];
668
+ if (!this.isSubscriber(value)) continue;
669
+ this.registerSubscriber(value);
670
+ this.hmrHandler?.trackHandler(fullPath, value);
671
+ this.bus.logger.utils.registration(value.name, relativePath);
672
+ }
673
+ });
674
+ }
675
+ registerSubscriber(ctor) {
676
+ this.bus.register(registrationFor(ctor));
677
+ }
678
+ unregisterSubscriber(ctor, artifacts) {
679
+ this.bus.unregister(ctor, artifacts);
680
+ }
681
+ getArtifacts(ctor) {
682
+ return [Reflect.getMetadata(SubscribeMetadataKey, ctor).subscriber];
683
+ }
684
+ isSubscriber(value) {
685
+ if (typeof value !== "function") return false;
686
+ return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);
687
+ }
688
+ };
689
+
690
+ //#endregion
691
+ //#region src/node/commands/CommandRegistry.ts
692
+ function indexById(commands) {
693
+ return new Map(commands.map((command) => [command.id, command]));
694
+ }
695
+ /**
696
+ * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.
697
+ *
698
+ * @internal
699
+ */
700
+ var CommandRegistry = class {
701
+ options;
702
+ logger = new Logger("Commands", { channel: "commands" });
703
+ isInitialised = false;
704
+ globalCommands = [];
705
+ guildCommands = /* @__PURE__ */ new Map();
706
+ ctorToCommand = /* @__PURE__ */ new Map();
707
+ loading = false;
708
+ loadedCommands = [];
709
+ hmrHandler;
710
+ pendingEvents = /* @__PURE__ */ new Map();
711
+ constructor(options) {
712
+ this.options = options;
713
+ if (!options.dir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
714
+ if (!Envapter.isDevelopment && !Envapter.isTest) return;
715
+ this.hmrHandler = new HmrModuleHandler({
716
+ handlersDir: options.dir,
717
+ isHandler: this.isCommandClass.bind(this),
718
+ registerHandler: this.registerCommand.bind(this),
719
+ unregisterHandler: this.unregisterCommand.bind(this),
720
+ getArtifacts: this.getArtifacts.bind(this),
721
+ logger: this.logger
722
+ });
723
+ }
724
+ getArtifacts(ctor) {
725
+ return this.ctorToCommand.get(ctor);
726
+ }
727
+ /** @internal */
728
+ async init() {
729
+ if (this.isInitialised) return;
730
+ this.isInitialised = true;
731
+ this.loading = true;
732
+ this.loadedCommands.length = 0;
733
+ try {
734
+ await this.loadCommands(this.options.dir);
735
+ } finally {
736
+ this.loading = false;
737
+ }
738
+ this.reportLoad();
739
+ getDevChannel()?.on("seedcord:refresh-commands", (data) => {
740
+ this.refresh(data.shouldRefresh);
741
+ });
742
+ }
743
+ reportLoad() {
744
+ const { utils } = this.logger;
745
+ utils.summary("Loaded commands", {
746
+ global: this.globalCommands.length,
747
+ "guild groups": this.guildCommands.size
748
+ });
749
+ let slash = 0;
750
+ let menu = 0;
751
+ for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
752
+ else menu++;
753
+ utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
754
+ "slash commands": slash,
755
+ "context menus": menu
756
+ })]);
757
+ }
758
+ /** @internal */
759
+ async refresh(shouldRefresh = true) {
760
+ if (!shouldRefresh) {
761
+ this.logger.info(chalk.italic("Command refresh cancelled."));
762
+ this.pendingEvents.clear();
763
+ return;
764
+ }
765
+ this.logger.info(chalk.italic("Refreshing commands..."));
766
+ for (const event of this.pendingEvents.values()) await this.hmrHandler?.handle(event);
767
+ this.pendingEvents.clear();
768
+ await this.setCommands();
769
+ }
770
+ /** @internal */
771
+ async onHmr(event) {
772
+ if (event.file.startsWith(resolve(process.cwd(), this.options.dir))) {
773
+ this.pendingEvents.delete(event.file);
774
+ this.pendingEvents.set(event.file, event);
775
+ getDevChannel()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f)) });
776
+ } else await this.hmrHandler?.handle(event);
777
+ }
778
+ async loadCommands(dir) {
779
+ await traverseDirectory(dir, (fullPath, rel, mod) => {
780
+ for (const exported of Object.values(mod)) if (this.isCommandClass(exported)) {
781
+ this.registerCommand(exported, rel);
782
+ this.hmrHandler?.trackHandler(fullPath, exported);
783
+ }
784
+ });
785
+ }
786
+ isCommandClass(obj) {
787
+ if (typeof obj !== "function") return false;
788
+ return obj.prototype instanceof BuilderComponent && Reflect.hasMetadata(CommandMetadataKey, obj);
789
+ }
790
+ registerCommand(ctor, rel) {
791
+ const meta = Reflect.getMetadata(CommandMetadataKey, ctor);
792
+ if (!meta) return;
793
+ const comp = new ctor().component;
794
+ const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
795
+ if (meta.scope === "global") this.globalCommands.push(comp);
796
+ else for (const g of meta.guilds) {
797
+ const arr = this.guildCommands.get(g) ?? [];
798
+ arr.push(comp);
799
+ this.guildCommands.set(g, arr);
800
+ }
801
+ this.ctorToCommand.set(ctor, {
802
+ name: comp.name,
803
+ scope: meta.scope,
804
+ ...meta.scope === "guild" && { guilds: meta.guilds }
805
+ });
806
+ if (this.loading) {
807
+ this.loadedCommands.push({
808
+ name: comp.name,
809
+ from: formatFilePath(rel),
810
+ kind
811
+ });
812
+ return;
813
+ }
814
+ this.logger.utils.registration(comp.name, rel, `${meta.scope} ${kind}`);
815
+ }
816
+ unregisterCommand(ctor, artifacts) {
817
+ const info = artifacts ?? this.ctorToCommand.get(ctor);
818
+ if (!info) return;
819
+ if (info.scope === "global") {
820
+ const idx = this.globalCommands.findIndex((c) => c.name === info.name);
821
+ if (idx !== -1) this.globalCommands.splice(idx, 1);
822
+ } else for (const g of info.guilds ?? []) {
823
+ const arr = this.guildCommands.get(g);
824
+ if (arr) {
825
+ const idx = arr.findIndex((c) => c.name === info.name);
826
+ if (idx !== -1) arr.splice(idx, 1);
827
+ }
828
+ }
829
+ this.ctorToCommand.delete(ctor);
830
+ }
831
+ /** @internal */
832
+ async setCommands() {
833
+ const result = {
834
+ global: /* @__PURE__ */ new Map(),
835
+ guilds: /* @__PURE__ */ new Map()
836
+ };
837
+ const appId = this.options.applicationId();
838
+ if (this.globalCommands.length > 0) {
839
+ result.global = indexById(await this.put(Routes.applicationCommands(appId), this.globalCommands));
840
+ const tag = this.globalCommands.length === 1 ? "command" : "commands";
841
+ this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
842
+ }
843
+ for (const [guildId, commands] of this.guildCommands.entries()) {
844
+ const deployed = await this.put(Routes.applicationGuildCommands(appId, guildId), commands);
845
+ result.guilds.set(guildId, indexById(deployed));
846
+ const tag = commands.length === 1 ? "command" : "commands";
847
+ this.logger.utils.block(`Deployed ${commands.length} ${tag} to ${paint.amber.bold(guildId)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
848
+ }
849
+ this.options.onDeployed?.(result);
850
+ return result;
851
+ }
852
+ async put(route, commands) {
853
+ const body = commands.map((command) => command.toJSON());
854
+ return await this.options.rest.put(route, { body });
855
+ }
856
+ /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */
857
+ allCommands() {
858
+ return [...this.globalCommands, ...this.guildCommands.values()].flat();
859
+ }
624
860
  /** @internal */
625
- removeListener(event, listener) {
626
- return super.removeListener(event, listener);
861
+ routeLeaves() {
862
+ return slashRouteLeaves(this.allCommands());
627
863
  }
628
864
  /** @internal */
629
- removeAllListeners(event) {
630
- return super.removeAllListeners(event);
865
+ contextMenuLeaves() {
866
+ return contextMenuLeaves(this.allCommands());
631
867
  }
632
868
  };
633
869
 
634
870
  //#endregion
635
- export { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, settleWithin };
871
+ export { CommandRegistry, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin };
636
872
  //# sourceMappingURL=node-internal.index.mjs.map