@seedcord/core 0.10.0 → 0.11.0
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/README.md +27 -9
- package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
- package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
- package/dist/PluginHost-B8kxLOeb.mjs +245 -0
- package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
- package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
- package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
- package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
- package/dist/ServerHost-CtmAhEXn.mjs +168 -0
- package/dist/ServerHost-CtmAhEXn.mjs.map +1 -0
- package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
- package/dist/bindViteEnv-BgFewlZ0.d.mts +11 -0
- package/dist/bindViteEnv-DWq5rftJ.mjs +88 -0
- package/dist/bindViteEnv-DWq5rftJ.mjs.map +1 -0
- package/dist/env-internal.index.d.mts +2 -0
- package/dist/env-internal.index.mjs +3 -0
- package/dist/hmr.index.d.mts +0 -1
- package/dist/hmr.index.mjs +1 -1
- package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
- package/dist/index.d.mts +19 -84
- package/dist/index.mjs +17 -37
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +186 -22
- package/dist/internal.index.mjs +714 -19
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +25 -70
- package/dist/node-internal.index.mjs +83 -297
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +3 -2
- package/dist/node.index.mjs +53 -200
- package/dist/node.index.mjs.map +1 -1
- package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
- package/dist/plugin.index.d.mts +2 -2
- package/dist/plugin.index.mjs +2 -2
- package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
- package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
- package/dist/subscribers-Ce3SWy23.mjs +1913 -0
- package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
- package/package.json +24 -17
- package/dist/Bus-BxPa-Uvp.mjs +0 -866
- package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
- package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
- package/dist/Pluggable-CwKbLui3.d.mts +0 -162
- package/dist/Pluggable-afy2H0Dr.mjs +0 -339
- package/dist/Pluggable-afy2H0Dr.mjs.map +0 -1
- package/dist/Plugin-CmNW5n6a.mjs +0 -122
- package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
- package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
- package/dist/cursor-CgNBnJfu.mjs +0 -802
- package/dist/cursor-CgNBnJfu.mjs.map +0 -1
- package/dist/devChannel-CQU7hq7Q.mjs +0 -14
- package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
- package/dist/index-B_-EjyvS.d.mts +0 -135
- package/dist/publishDefault-BuSZPbjT.mjs +0 -14
- package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
- package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
- package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
- package/dist/subscribers-xGy7a7xR.mjs +0 -167
- package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
- package/dist/types-BG0BimVr.d.mts +0 -13
|
@@ -1,47 +1,91 @@
|
|
|
1
|
-
import { a as RegisterSubscriber, c as VerifyWebhooks, f as Subscriber, g as CommandMetadataKey, i as RegisterDefaults, k as contextMenuLeaves, n as busLoggerOf, o as RegisteredCount, r as registrationFor, s as UnregisterSubscriber, w as SubscribeMetadataKey } from "./Bus-BxPa-Uvp.mjs";
|
|
2
|
-
import { r as CommandInjector, t as ConfigGuilds } from "./subscribers-xGy7a7xR.mjs";
|
|
3
|
-
import { t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
|
|
4
1
|
import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { n as
|
|
7
|
-
import { t as HmrModuleHandler } from "./HmrModuleHandler-DOAtah3j.mjs";
|
|
8
|
-
import { i as settleWithin, n as shutdownOf, r as assertDeclaredRuntime } from "./Pluggable-afy2H0Dr.mjs";
|
|
9
|
-
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
10
|
-
import { Routes } from "discord-api-types/v10";
|
|
2
|
+
import { n as settleWithin } from "./PluginLifecycle-AhQ46NIr.mjs";
|
|
3
|
+
import { n as shutdownOf, r as assertDeclaredRuntime } from "./ServerHost-CtmAhEXn.mjs";
|
|
11
4
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
12
5
|
import { SeedcordError } from "@seedcord/errors/internal";
|
|
13
6
|
import { Logger } from "@seedcord/logger";
|
|
14
|
-
import {
|
|
7
|
+
import { botConfigDir, isPortInUse, setBuiltFilesHook } from "@seedcord/utils/internal";
|
|
15
8
|
import { Envapter } from "envapt";
|
|
16
|
-
import { resolve } from "node:path";
|
|
17
9
|
import { createServer } from "node:http";
|
|
18
|
-
import { traverseDirectory } from "@seedcord/utils/node";
|
|
19
10
|
|
|
20
11
|
//#region src/node/Lifecycle/drainWindow.ts
|
|
21
12
|
const DRAIN_WINDOW_MS = 5e3;
|
|
22
13
|
const DRAIN_TASK_TIMEOUT_MS = 6e3;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/node/Lifecycle/InFlight.ts
|
|
17
|
+
var InFlight = class {
|
|
18
|
+
logger;
|
|
19
|
+
label;
|
|
20
|
+
running = /* @__PURE__ */ new Set();
|
|
21
|
+
isClosed = false;
|
|
22
|
+
constructor(logger, label) {
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.label = label;
|
|
25
|
+
}
|
|
26
|
+
get closed() {
|
|
27
|
+
return this.isClosed;
|
|
28
|
+
}
|
|
29
|
+
close() {
|
|
30
|
+
this.isClosed = true;
|
|
31
|
+
}
|
|
32
|
+
track(work) {
|
|
33
|
+
this.running.add(work);
|
|
34
|
+
const forget = () => {
|
|
35
|
+
this.running.delete(work);
|
|
36
|
+
};
|
|
37
|
+
work.then(forget, forget);
|
|
38
|
+
}
|
|
39
|
+
async drain(timeoutMs) {
|
|
40
|
+
await settleWithin(this.settleAll(), timeoutMs);
|
|
41
|
+
if (this.running.size > 0) this.logger.warn(`${this.label}: ${String(this.running.size)} still running when the drain window closed`);
|
|
42
|
+
}
|
|
43
|
+
async settleAll() {
|
|
44
|
+
while (this.running.size > 0) await Promise.allSettled(this.running);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
27
47
|
|
|
28
48
|
//#endregion
|
|
29
49
|
//#region src/node/HealthResponder.ts
|
|
30
50
|
const HTTP_OK = 200;
|
|
51
|
+
const HTTP_SERVICE_UNAVAILABLE = 503;
|
|
31
52
|
const DEFAULT_HEALTH_CHECK_PATH = "/health";
|
|
32
|
-
|
|
53
|
+
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
54
|
+
function healthCheckConfig(option) {
|
|
55
|
+
if (option === false) return void 0;
|
|
56
|
+
return typeof option === "object" ? option : {};
|
|
57
|
+
}
|
|
58
|
+
var HealthResponder = class HealthResponder {
|
|
33
59
|
path;
|
|
34
60
|
constructor(path) {
|
|
35
61
|
this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;
|
|
36
62
|
}
|
|
63
|
+
static fromOption(option) {
|
|
64
|
+
const config = healthCheckConfig(option);
|
|
65
|
+
return config && new HealthResponder(config.path);
|
|
66
|
+
}
|
|
37
67
|
tryRespond(req, res) {
|
|
38
|
-
if (req.method
|
|
39
|
-
res.writeHead(HTTP_OK,
|
|
40
|
-
res.end(
|
|
68
|
+
if (!this.matches(req.method, req.url)) return false;
|
|
69
|
+
res.writeHead(HTTP_OK, JSON_HEADERS);
|
|
70
|
+
res.end(this.body());
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
answer(request, ready) {
|
|
74
|
+
if (!this.matches(request.method, new URL(request.url).pathname)) return void 0;
|
|
75
|
+
if (!ready) return new Response(null, { status: HTTP_SERVICE_UNAVAILABLE });
|
|
76
|
+
return new Response(this.body(), {
|
|
77
|
+
status: HTTP_OK,
|
|
78
|
+
headers: JSON_HEADERS
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
matches(method, path) {
|
|
82
|
+
return method === "GET" && path === this.path;
|
|
83
|
+
}
|
|
84
|
+
body() {
|
|
85
|
+
return JSON.stringify({
|
|
41
86
|
status: "ok",
|
|
42
87
|
timestamp: Date.now()
|
|
43
|
-
})
|
|
44
|
-
return true;
|
|
88
|
+
});
|
|
45
89
|
}
|
|
46
90
|
};
|
|
47
91
|
|
|
@@ -71,8 +115,8 @@ var HealthCheck = class HealthCheck {
|
|
|
71
115
|
return this.responder.path;
|
|
72
116
|
}
|
|
73
117
|
static fromOption(shutdown, option) {
|
|
74
|
-
|
|
75
|
-
return new HealthCheck(shutdown,
|
|
118
|
+
const config = healthCheckConfig(option);
|
|
119
|
+
return config && new HealthCheck(shutdown, config);
|
|
76
120
|
}
|
|
77
121
|
async init() {
|
|
78
122
|
return new Promise((resolve, reject) => {
|
|
@@ -82,13 +126,19 @@ var HealthCheck = class HealthCheck {
|
|
|
82
126
|
res.end(JSON.stringify({ status: "not found" }));
|
|
83
127
|
});
|
|
84
128
|
this.server = server;
|
|
85
|
-
const onListenError = (
|
|
129
|
+
const onListenError = (error) => {
|
|
130
|
+
if (isPortInUse(error)) {
|
|
131
|
+
reject(new SeedcordError(SeedcordErrorCode.PortInUse, [this._port, "healthCheck.port"], { cause: error }));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
reject(error);
|
|
135
|
+
};
|
|
86
136
|
server.on("error", onListenError);
|
|
87
137
|
server.once("listening", () => {
|
|
88
138
|
server.removeListener("error", onListenError);
|
|
89
139
|
server.on("error", (err) => this.logger.error("Health check server error", err));
|
|
90
140
|
const address = this._host ?? "localhost";
|
|
91
|
-
this.logger.info(`${paint.mint.bold(
|
|
141
|
+
this.logger.info(`${paint.mint.bold(paint.check)} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`);
|
|
92
142
|
resolve();
|
|
93
143
|
});
|
|
94
144
|
if (this._host) {
|
|
@@ -117,276 +167,12 @@ var HealthCheck = class HealthCheck {
|
|
|
117
167
|
};
|
|
118
168
|
|
|
119
169
|
//#endregion
|
|
120
|
-
//#region src/node/
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
constructor(bus, directory) {
|
|
127
|
-
this.bus = bus;
|
|
128
|
-
this.directory = directory;
|
|
129
|
-
if (!this.directory) return;
|
|
130
|
-
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
131
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
132
|
-
handlersDir: this.directory,
|
|
133
|
-
isHandler: this.isSubscriber.bind(this),
|
|
134
|
-
registerHandler: this.registerSubscriber.bind(this),
|
|
135
|
-
unregisterHandler: this.unregisterSubscriber.bind(this),
|
|
136
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
137
|
-
logger: busLoggerOf(this.bus)
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
/** @internal */
|
|
141
|
-
async init() {
|
|
142
|
-
if (this.isInitialized) return;
|
|
143
|
-
this.isInitialized = true;
|
|
144
|
-
this.bus[RegisterDefaults]();
|
|
145
|
-
const { directory } = this;
|
|
146
|
-
if (directory) {
|
|
147
|
-
busLoggerOf(this.bus).debug(paint.mute(directory));
|
|
148
|
-
await this.load(directory);
|
|
149
|
-
busLoggerOf(this.bus).utils.list([`${paint.iris.bold(this.bus[RegisteredCount])} subscribers`], paint.mint.bold("Loaded"), "debug");
|
|
150
|
-
}
|
|
151
|
-
if (!Envapter.isTest) await this.bus[VerifyWebhooks]();
|
|
152
|
-
}
|
|
153
|
-
/** @internal */
|
|
154
|
-
async onHmr(event) {
|
|
155
|
-
if (this.hmrHandler) await this.hmrHandler.handle(event);
|
|
156
|
-
}
|
|
157
|
-
async load(dir) {
|
|
158
|
-
await traverseDirectory(dir, (fullPath, relativePath, imported) => {
|
|
159
|
-
for (const exportName of Object.keys(imported)) {
|
|
160
|
-
const value = imported[exportName];
|
|
161
|
-
if (!this.isSubscriber(value)) continue;
|
|
162
|
-
this.registerSubscriber(value);
|
|
163
|
-
this.hmrHandler?.trackHandler(fullPath, value);
|
|
164
|
-
busLoggerOf(this.bus).utils.registration(value.name, relativePath, void 0, "trace");
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
registerSubscriber(ctor) {
|
|
169
|
-
this.bus[RegisterSubscriber](registrationFor(ctor));
|
|
170
|
-
}
|
|
171
|
-
unregisterSubscriber(ctor, artifacts) {
|
|
172
|
-
this.bus[UnregisterSubscriber](ctor, artifacts);
|
|
173
|
-
}
|
|
174
|
-
getArtifacts(ctor) {
|
|
175
|
-
return [Reflect.getMetadata(SubscribeMetadataKey, ctor).subscriber];
|
|
176
|
-
}
|
|
177
|
-
isSubscriber(value) {
|
|
178
|
-
if (typeof value !== "function") return false;
|
|
179
|
-
return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
//#endregion
|
|
184
|
-
//#region src/commands/deployTarget.ts
|
|
185
|
-
function resolveDeployTarget(meta, configured, commandName) {
|
|
186
|
-
if (meta.scope === "global") return { scope: "global" };
|
|
187
|
-
if (meta.scope === "config") return configured.length > 0 ? guildTarget(configured) : { scope: "global" };
|
|
188
|
-
if (configured.length === 0 && meta.guilds.includes("config")) throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);
|
|
189
|
-
return guildTarget(meta.guilds.flatMap((target) => target === "config" ? configured : target));
|
|
190
|
-
}
|
|
191
|
-
function guildTarget(guilds) {
|
|
192
|
-
return {
|
|
193
|
-
scope: "guild",
|
|
194
|
-
guilds: [...new Set(guilds)]
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region src/node/commands/CommandRegistry.ts
|
|
200
|
-
function indexById(commands) {
|
|
201
|
-
return new Map(commands.map((command) => [command.id, command]));
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.
|
|
205
|
-
*
|
|
206
|
-
* @internal
|
|
207
|
-
*/
|
|
208
|
-
var CommandRegistry = class {
|
|
209
|
-
core;
|
|
210
|
-
logger = new Logger("Commands", { channel: "commands" });
|
|
211
|
-
isInitialised = false;
|
|
212
|
-
globalCommands = [];
|
|
213
|
-
guildCommands = /* @__PURE__ */ new Map();
|
|
214
|
-
ctorToCommand = /* @__PURE__ */ new Map();
|
|
215
|
-
loading = false;
|
|
216
|
-
loadedCommands = [];
|
|
217
|
-
hmrHandler;
|
|
218
|
-
pendingEvents = /* @__PURE__ */ new Map();
|
|
219
|
-
injector = new CommandInjector();
|
|
220
|
-
dir;
|
|
221
|
-
configuredGuilds;
|
|
222
|
-
constructor(core) {
|
|
223
|
-
this.core = core;
|
|
224
|
-
const { commands } = core.config.bot;
|
|
225
|
-
if (!commands.path) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
226
|
-
this.dir = commands.path;
|
|
227
|
-
this.configuredGuilds = commands.guilds ?? [];
|
|
228
|
-
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
229
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
230
|
-
handlersDir: this.dir,
|
|
231
|
-
isHandler: isCommandClass,
|
|
232
|
-
registerHandler: this.registerCommand.bind(this),
|
|
233
|
-
unregisterHandler: this.unregisterCommand.bind(this),
|
|
234
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
235
|
-
logger: this.logger
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
getArtifacts(ctor) {
|
|
239
|
-
return this.ctorToCommand.get(ctor);
|
|
240
|
-
}
|
|
241
|
-
/** @internal */
|
|
242
|
-
async init() {
|
|
243
|
-
if (this.isInitialised) return;
|
|
244
|
-
this.isInitialised = true;
|
|
245
|
-
this.loading = true;
|
|
246
|
-
this.loadedCommands.length = 0;
|
|
247
|
-
try {
|
|
248
|
-
await this.loadCommands(this.dir);
|
|
249
|
-
} finally {
|
|
250
|
-
this.loading = false;
|
|
251
|
-
}
|
|
252
|
-
this.reportLoad();
|
|
253
|
-
getDevChannel()?.on("seedcord:refresh-commands", (data) => {
|
|
254
|
-
this.refresh(data.shouldRefresh);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
reportLoad() {
|
|
258
|
-
const { utils } = this.logger;
|
|
259
|
-
utils.summary("Loaded commands", {
|
|
260
|
-
global: this.globalCommands.length,
|
|
261
|
-
"guild groups": this.guildCommands.size
|
|
262
|
-
}, "debug");
|
|
263
|
-
let slash = 0;
|
|
264
|
-
let menu = 0;
|
|
265
|
-
for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
|
|
266
|
-
else menu++;
|
|
267
|
-
utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
|
|
268
|
-
"slash commands": slash,
|
|
269
|
-
"context menus": menu
|
|
270
|
-
})], "debug");
|
|
271
|
-
}
|
|
272
|
-
/** @internal */
|
|
273
|
-
async refresh(shouldRefresh = true) {
|
|
274
|
-
if (!shouldRefresh) {
|
|
275
|
-
this.logger.debug(paint.italic("Command refresh cancelled."));
|
|
276
|
-
this.pendingEvents.clear();
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
this.logger.debug(paint.italic("Refreshing commands..."));
|
|
280
|
-
for (const event of this.pendingEvents.values()) await this.hmrHandler?.handle(event);
|
|
281
|
-
this.pendingEvents.clear();
|
|
282
|
-
await this.setCommands();
|
|
283
|
-
}
|
|
284
|
-
/** @internal */
|
|
285
|
-
async onHmr(event) {
|
|
286
|
-
if (event.file.startsWith(resolve(process.cwd(), this.dir))) {
|
|
287
|
-
this.pendingEvents.delete(event.file);
|
|
288
|
-
this.pendingEvents.set(event.file, event);
|
|
289
|
-
getDevChannel()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f)) });
|
|
290
|
-
} else await this.hmrHandler?.handle(event);
|
|
291
|
-
}
|
|
292
|
-
async loadCommands(dir) {
|
|
293
|
-
await traverseDirectory(dir, (fullPath, rel, mod) => {
|
|
294
|
-
for (const exported of Object.values(mod)) if (isCommandClass(exported)) {
|
|
295
|
-
this.registerCommand(exported, rel);
|
|
296
|
-
this.hmrHandler?.trackHandler(fullPath, exported);
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
registerCommand(Ctor, rel) {
|
|
301
|
-
const meta = Reflect.getMetadata(CommandMetadataKey, Ctor);
|
|
302
|
-
if (!meta) return;
|
|
303
|
-
const comp = new Ctor().component;
|
|
304
|
-
const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
|
|
305
|
-
const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);
|
|
306
|
-
if (target.scope === "global") this.globalCommands.push(comp);
|
|
307
|
-
else for (const g of target.guilds) {
|
|
308
|
-
const arr = this.guildCommands.get(g) ?? [];
|
|
309
|
-
arr.push(comp);
|
|
310
|
-
this.guildCommands.set(g, arr);
|
|
311
|
-
}
|
|
312
|
-
this.ctorToCommand.set(Ctor, {
|
|
313
|
-
name: comp.name,
|
|
314
|
-
...target
|
|
315
|
-
});
|
|
316
|
-
if (this.loading) this.loadedCommands.push({
|
|
317
|
-
name: comp.name,
|
|
318
|
-
from: formatFilePath(rel),
|
|
319
|
-
kind
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
unregisterCommand(ctor, artifacts) {
|
|
323
|
-
const info = artifacts ?? this.ctorToCommand.get(ctor);
|
|
324
|
-
if (!info) return;
|
|
325
|
-
if (info.scope === "global") {
|
|
326
|
-
const idx = this.globalCommands.findIndex((c) => c.name === info.name);
|
|
327
|
-
if (idx !== -1) this.globalCommands.splice(idx, 1);
|
|
328
|
-
} else for (const g of info.guilds) {
|
|
329
|
-
const arr = this.guildCommands.get(g);
|
|
330
|
-
if (!arr) continue;
|
|
331
|
-
const idx = arr.findIndex((c) => c.name === info.name);
|
|
332
|
-
if (idx !== -1) arr.splice(idx, 1);
|
|
333
|
-
}
|
|
334
|
-
this.ctorToCommand.delete(ctor);
|
|
335
|
-
}
|
|
336
|
-
/** @internal */
|
|
337
|
-
async setCommands() {
|
|
338
|
-
const result = {
|
|
339
|
-
global: /* @__PURE__ */ new Map(),
|
|
340
|
-
guilds: /* @__PURE__ */ new Map()
|
|
341
|
-
};
|
|
342
|
-
const appId = this.core.applicationId;
|
|
343
|
-
if (this.globalCommands.length > 0) {
|
|
344
|
-
result.global = indexById(await this.put(Routes.applicationCommands(appId), this.globalCommands));
|
|
345
|
-
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
346
|
-
this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
|
|
347
|
-
}
|
|
348
|
-
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
349
|
-
const deployed = await this.putGuild(appId, guildId, commands);
|
|
350
|
-
result.guilds.set(guildId, indexById(deployed));
|
|
351
|
-
const tag = commands.length === 1 ? "command" : "commands";
|
|
352
|
-
this.logger.utils.block(`Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
|
|
353
|
-
}
|
|
354
|
-
this.injector.inject(result, this.allCommands());
|
|
355
|
-
this.core.bus[PublishDefault]("commandsDeployed", deployedPayload(result));
|
|
356
|
-
return result;
|
|
357
|
-
}
|
|
358
|
-
async putGuild(appId, guildId, commands) {
|
|
359
|
-
try {
|
|
360
|
-
return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);
|
|
361
|
-
} catch (error) {
|
|
362
|
-
throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
async put(route, commands) {
|
|
366
|
-
const body = commands.map((command) => command.toJSON());
|
|
367
|
-
const { rest } = this.core;
|
|
368
|
-
return await rest.put(route, { body });
|
|
369
|
-
}
|
|
370
|
-
/** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */
|
|
371
|
-
allCommands() {
|
|
372
|
-
return [...this.globalCommands, ...this.guildCommands.values()].flat();
|
|
373
|
-
}
|
|
374
|
-
/** @internal */
|
|
375
|
-
routeLeaves() {
|
|
376
|
-
return slashRouteLeaves(this.allCommands());
|
|
377
|
-
}
|
|
378
|
-
/** @internal */
|
|
379
|
-
contextMenuLeaves() {
|
|
380
|
-
return contextMenuLeaves(this.allCommands());
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
function deployedPayload(result) {
|
|
384
|
-
const guilds = {};
|
|
385
|
-
for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];
|
|
386
|
-
return {
|
|
387
|
-
global: [...result.global.values()],
|
|
388
|
-
guilds
|
|
389
|
-
};
|
|
170
|
+
//#region src/node/envaptBaseDir.ts
|
|
171
|
+
function setEnvaptBaseDir() {
|
|
172
|
+
setBuiltFilesHook(() => {
|
|
173
|
+
const configDir = botConfigDir();
|
|
174
|
+
if (configDir !== void 0 && Envapter.source.supportsFiles) Envapter.baseDir = configDir;
|
|
175
|
+
});
|
|
390
176
|
}
|
|
391
177
|
|
|
392
178
|
//#endregion
|
|
@@ -394,5 +180,5 @@ function deployedPayload(result) {
|
|
|
394
180
|
assertDeclaredRuntime();
|
|
395
181
|
|
|
396
182
|
//#endregion
|
|
397
|
-
export {
|
|
183
|
+
export { DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, HealthResponder, InFlight, ShutdownPhase, StartupPhase, setEnvaptBaseDir, settleWithin, shutdownOf };
|
|
398
184
|
//# sourceMappingURL=node-internal.index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/subscribers/SubscriberLoader.ts","../src/commands/deployTarget.ts","../src/node/commands/CommandRegistry.ts","../src/node-internal.index.ts"],"sourcesContent":["import { settleWithin } from './withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\n// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n\nexport async function drainInFlight(\n inFlight: ReadonlySet<Promise<unknown>>,\n timeoutMs: number,\n logger: Logger,\n label: string\n): Promise<void> {\n await settleWithin(Promise.allSettled(inFlight), timeoutMs);\n // nothing cancels these before the process exits\n if (inFlight.size > 0) logger.warn(`${label}: ${String(inFlight.size)} still running when the drain window closed`);\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (req.method !== 'GET' || req.url !== this.path) return false;\n\n res.writeHead(HTTP_OK, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));\n return true;\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n if (option === false) return undefined;\n return new HealthCheck(shutdown, typeof option === 'object' ? option : undefined);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (err: Error): void => reject(err);\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold('✔︎')} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Envapter } from 'envapt';\n\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\nimport { busLoggerOf } from '#subscribers/Bus';\nimport { registrationFor, Subscriber } from '#subscribers/index';\nimport {\n RegisterDefaults,\n RegisteredCount,\n RegisterSubscriber,\n UnregisterSubscriber,\n VerifyWebhooks\n} from '#subscribers/slots';\n\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { SubscribeMetadataEntry } from '#subscribers/decorators/Subscribe';\nimport type { StoredSubscriberCtor, Bus } from '#subscribers/index';\nimport type { SubscriptionKey } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\ntype SubscriberArtifact = SubscriptionKey[];\n\n// loads subscribers off the filesystem into a Bus and stores their hot-reload wiring. node only, a bundled isolate registers from the manifest instead\nexport class SubscriberLoader implements Initializeable, HmrAware {\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<StoredSubscriberCtor, void, SubscriberArtifact>;\n\n constructor(\n private readonly bus: Bus,\n private readonly directory: string | null\n ) {\n if (!this.directory) return;\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir: this.directory,\n isHandler: this.isSubscriber.bind(this),\n registerHandler: this.registerSubscriber.bind(this),\n unregisterHandler: this.unregisterSubscriber.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: busLoggerOf(this.bus)\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.bus[RegisterDefaults]();\n\n const { directory } = this;\n if (directory) {\n busLoggerOf(this.bus).debug(paint.mute(directory));\n await this.load(directory);\n busLoggerOf(this.bus).utils.list(\n [`${paint.iris.bold(this.bus[RegisteredCount])} subscribers`],\n paint.mint.bold('Loaded'),\n 'debug'\n );\n }\n\n if (!Envapter.isTest) await this.bus[VerifyWebhooks]();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (this.hmrHandler) await this.hmrHandler.handle(event);\n }\n\n private async load(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const exportName of Object.keys(imported)) {\n const value = imported[exportName];\n if (!this.isSubscriber(value)) continue;\n\n this.registerSubscriber(value);\n this.hmrHandler?.trackHandler(fullPath, value);\n busLoggerOf(this.bus).utils.registration(value.name, relativePath, undefined, 'trace');\n }\n });\n }\n\n private registerSubscriber(ctor: StoredSubscriberCtor): void {\n this.bus[RegisterSubscriber](registrationFor(ctor));\n }\n\n private unregisterSubscriber(ctor: StoredSubscriberCtor, artifacts?: SubscriberArtifact): void {\n this.bus[UnregisterSubscriber](ctor, artifacts);\n }\n\n private getArtifacts(ctor: StoredSubscriberCtor): SubscriberArtifact {\n const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry;\n return [meta.subscriber];\n }\n\n private isSubscriber(value: unknown): value is StoredSubscriberCtor {\n if (typeof value !== 'function') return false;\n return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ConfigGuilds } from '#decorators/Command';\n\nimport type { CommandMeta } from '#decorators/Command';\n\nexport type DeployTarget = Exclude<CommandMeta, { scope: 'config' }>;\n\nexport function resolveDeployTarget(\n meta: CommandMeta,\n configured: readonly string[],\n commandName: string\n): DeployTarget {\n if (meta.scope === 'global') return { scope: 'global' };\n\n if (meta.scope === 'config') {\n return configured.length > 0 ? guildTarget(configured) : { scope: 'global' };\n }\n\n if (configured.length === 0 && meta.guilds.includes(ConfigGuilds)) {\n throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);\n }\n\n return guildTarget(meta.guilds.flatMap((target) => (target === ConfigGuilds ? configured : target)));\n}\n\n// a repeated id would push the same builder into one guild twice, and discord rejects a duplicate name in the put\nfunction guildTarget(guilds: readonly string[]): DeployTarget {\n return { scope: 'guild', guilds: [...new Set(guilds)] };\n}\n","import { resolve } from 'node:path';\n\nimport { SlashCommandBuilder } from '@discordjs/builders';\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { getDevChannel } from '#hmr/devChannel';\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { CommandInjector } from '#src/commands/CommandInjector';\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { resolveDeployTarget } from '#src/commands/deployTarget';\nimport { isCommandClass } from '#src/commands/isCommandClass';\nimport { slashRouteLeaves } from '#src/commands/slashRouteLeaves';\nimport { CommandMetadataKey } from '#src/metadataKeys';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CommandMeta } from '#decorators/Command';\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ContextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport type { DeployTarget } from '#src/commands/deployTarget';\nimport type { CommandCtor } from '#src/commands/isCommandClass';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { AllSubscriptions } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { APIApplicationCommand } from 'discord-api-types/v10';\n\ntype CommandArtifact = DeployTarget & { name: string };\n\nfunction indexById(commands: readonly APIApplicationCommand[]): Map<string, APIApplicationCommand> {\n return new Map(commands.map((command) => [command.id, command]));\n}\n\n/**\n * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.\n *\n * @internal\n */\nexport class CommandRegistry implements Initializeable, HmrAware {\n private readonly logger = new Logger('Commands', { channel: 'commands' });\n private isInitialised = false;\n\n public readonly globalCommands: CommandBuilder[] = [];\n public readonly guildCommands = new Map<string, CommandBuilder[]>();\n\n private readonly ctorToCommand = new Map<CommandCtor, CommandArtifact>();\n\n // batched during bulk load. A reload reports on the hmr channel instead\n private loading = false;\n private readonly loadedCommands: { name: string; from: string; kind: 'slash command' | 'context menu' }[] = [];\n\n private readonly hmrHandler?: HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>;\n private readonly pendingEvents = new Map<string, HmrUpdateEvent>();\n private readonly injector = new CommandInjector();\n\n private readonly dir: string;\n private readonly configuredGuilds: readonly string[];\n\n public constructor(private readonly core: CoreBase) {\n const { commands } = core.config.bot;\n if (!commands.path) {\n throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ['CommandRegistry', 'commands']);\n }\n this.dir = commands.path;\n this.configuredGuilds = commands.guilds ?? [];\n\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n this.hmrHandler = new HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>({\n handlersDir: this.dir,\n isHandler: isCommandClass,\n registerHandler: this.registerCommand.bind(this),\n unregisterHandler: this.unregisterCommand.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.logger\n });\n }\n\n private getArtifacts(ctor: CommandCtor): CommandArtifact | undefined {\n return this.ctorToCommand.get(ctor);\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialised) return;\n this.isInitialised = true;\n\n this.loading = true;\n this.loadedCommands.length = 0;\n try {\n await this.loadCommands(this.dir);\n } finally {\n this.loading = false;\n }\n\n this.reportLoad();\n\n getDevChannel()?.on('seedcord:refresh-commands', (data) => {\n void this.refresh(data.shouldRefresh);\n });\n }\n\n private reportLoad(): void {\n const { utils } = this.logger;\n\n utils.summary(\n 'Loaded commands',\n { global: this.globalCommands.length, 'guild groups': this.guildCommands.size },\n 'debug'\n );\n\n let slash = 0;\n let menu = 0;\n for (const command of this.loadedCommands) {\n if (command.kind === 'slash command') slash++;\n else menu++;\n }\n\n utils.block(\n 'Loaded commands',\n [\n ...utils.entries(this.loadedCommands),\n ...utils.counts({ 'slash commands': slash, 'context menus': menu })\n ],\n 'debug'\n );\n }\n\n /** @internal */\n public async refresh(shouldRefresh = true): Promise<void> {\n if (!shouldRefresh) {\n this.logger.debug(paint.italic('Command refresh cancelled.'));\n this.pendingEvents.clear();\n return;\n }\n\n this.logger.debug(paint.italic('Refreshing commands...'));\n for (const event of this.pendingEvents.values()) {\n await this.hmrHandler?.handle(event);\n }\n this.pendingEvents.clear();\n await this.setCommands();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (event.file.startsWith(resolve(process.cwd(), this.dir))) {\n this.pendingEvents.delete(event.file);\n this.pendingEvents.set(event.file, event);\n getDevChannel()?.send('seedcord:commands-update-prompt', {\n files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f))\n });\n } else {\n await this.hmrHandler?.handle(event);\n }\n }\n\n private async loadCommands(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, rel, mod) => {\n for (const exported of Object.values(mod))\n if (isCommandClass(exported)) {\n this.registerCommand(exported, rel);\n this.hmrHandler?.trackHandler(fullPath, exported);\n }\n });\n }\n\n private registerCommand(Ctor: CommandCtor, rel: string): void {\n const meta = Reflect.getMetadata(CommandMetadataKey, Ctor) as CommandMeta | undefined;\n\n if (!meta) return;\n\n const instance = new Ctor();\n const comp = instance.component;\n const kind = comp instanceof SlashCommandBuilder ? 'slash command' : 'context menu';\n\n const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);\n\n if (target.scope === 'global') {\n this.globalCommands.push(comp);\n } else {\n for (const g of target.guilds) {\n const arr = this.guildCommands.get(g) ?? [];\n arr.push(comp);\n this.guildCommands.set(g, arr);\n }\n }\n\n this.ctorToCommand.set(Ctor, { name: comp.name, ...target });\n\n if (this.loading) {\n this.loadedCommands.push({ name: comp.name, from: formatFilePath(rel), kind });\n }\n }\n\n private unregisterCommand(ctor: CommandCtor, artifacts?: CommandArtifact): void {\n const info = artifacts ?? this.ctorToCommand.get(ctor);\n if (!info) return;\n\n if (info.scope === 'global') {\n const idx = this.globalCommands.findIndex((c) => c.name === info.name);\n if (idx !== -1) this.globalCommands.splice(idx, 1);\n } else {\n for (const g of info.guilds) {\n const arr = this.guildCommands.get(g);\n if (!arr) continue;\n const idx = arr.findIndex((c) => c.name === info.name);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n this.ctorToCommand.delete(ctor);\n }\n\n /** @internal */\n public async setCommands(): Promise<DeployResult> {\n const result: DeployResult = { global: new Map(), guilds: new Map() };\n const appId = this.core.applicationId;\n\n if (this.globalCommands.length > 0) {\n const deployed = await this.put(Routes.applicationCommands(appId), this.globalCommands);\n result.global = indexById(deployed);\n const tag = this.globalCommands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${this.globalCommands.length} global ${tag}`,\n this.logger.utils.wrap(this.globalCommands.map((command) => command.name)),\n 'info'\n );\n }\n\n for (const [guildId, commands] of this.guildCommands.entries()) {\n const deployed = await this.putGuild(appId, guildId, commands);\n result.guilds.set(guildId, indexById(deployed));\n const tag = commands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`,\n this.logger.utils.wrap(commands.map((command) => command.name)),\n 'info'\n );\n }\n\n this.injector.inject(result, this.allCommands());\n this.core.bus[PublishDefault]('commandsDeployed', deployedPayload(result));\n return result;\n }\n\n private async putGuild(\n appId: string,\n guildId: string,\n commands: readonly CommandBuilder[]\n ): Promise<APIApplicationCommand[]> {\n try {\n return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);\n } catch (error) {\n throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });\n }\n }\n\n private async put(route: `/${string}`, commands: readonly CommandBuilder[]): Promise<APIApplicationCommand[]> {\n const body = commands.map((command) => command.toJSON());\n // read here because gateway assigns core.rest after this registry is constructed\n const { rest } = this.core;\n // justified: the bulk-overwrite routes return the deployed command array\n return (await rest.put(route, { body })) as APIApplicationCommand[];\n }\n\n /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */\n public allCommands(): CommandBuilder[] {\n return [...this.globalCommands, ...this.guildCommands.values()].flat();\n }\n\n /** @internal */\n public routeLeaves(): Set<string> {\n return slashRouteLeaves(this.allCommands());\n }\n\n /** @internal */\n public contextMenuLeaves(): ContextMenuLeaves {\n return contextMenuLeaves(this.allCommands());\n }\n}\n\nfunction deployedPayload(result: DeployResult): AllSubscriptions['commandsDeployed'] {\n const guilds: Record<string, APIApplicationCommand[]> = {};\n for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];\n\n return { global: [...result.global.values()], guilds };\n}\n","import { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\n\n// the edge bundle never reaches this entry\nassertDeclaredRuntime();\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#node/Lifecycle/withTimeout';\nexport { SubscriberLoader } from '#node/subscribers/SubscriberLoader';\nexport { CommandRegistry } from '#node/commands/CommandRegistry';\nexport { shutdownOf } from '#node/Pluggable';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/Lifecycle/InFlight.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/envaptBaseDir.ts","../src/node-internal.index.ts"],"sourcesContent":["// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n","import { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\nexport class InFlight {\n private readonly running = new Set<Promise<unknown>>();\n private isClosed = false;\n\n constructor(\n private readonly logger: Logger,\n private readonly label: string\n ) {}\n\n public get closed(): boolean {\n return this.isClosed;\n }\n\n public close(): void {\n this.isClosed = true;\n }\n\n public track(work: Promise<unknown>): void {\n this.running.add(work);\n const forget = (): void => {\n this.running.delete(work);\n };\n void work.then(forget, forget);\n }\n\n public async drain(timeoutMs: number): Promise<void> {\n await settleWithin(this.settleAll(), timeoutMs);\n // nothing cancels these before the process exits\n if (this.running.size > 0) {\n this.logger.warn(`${this.label}: ${String(this.running.size)} still running when the drain window closed`);\n }\n }\n\n // settling work can track more work\n private async settleAll(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n}\n","import type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst HTTP_SERVICE_UNAVAILABLE = 503;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nexport function healthCheckConfig(option?: HealthCheckOption): HealthCheckConfig | undefined {\n if (option === false) return undefined;\n return typeof option === 'object' ? option : {};\n}\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public static fromOption(option?: HealthCheckOption): HealthResponder | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthResponder(config.path);\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (!this.matches(req.method, req.url)) return false;\n\n res.writeHead(HTTP_OK, JSON_HEADERS);\n res.end(this.body());\n return true;\n }\n\n public answer(request: Request, ready: boolean): Response | undefined {\n if (!this.matches(request.method, new URL(request.url).pathname)) return undefined;\n if (!ready) return new Response(null, { status: HTTP_SERVICE_UNAVAILABLE });\n return new Response(this.body(), { status: HTTP_OK, headers: JSON_HEADERS });\n }\n\n private matches(method: string | undefined, path: string | undefined): boolean {\n return method === 'GET' && path === this.path;\n }\n\n private body(): string {\n return JSON.stringify({ status: 'ok', timestamp: Date.now() });\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint, SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { isPortInUse } from '@seedcord/utils/internal';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { healthCheckConfig, HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthCheck(shutdown, config);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (error: Error): void => {\n if (isPortInUse(error)) {\n reject(\n new SeedcordError(SeedcordErrorCode.PortInUse, [this._port, 'healthCheck.port'], {\n cause: error\n })\n );\n return;\n }\n reject(error);\n };\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold(paint.check)} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { botConfigDir, setBuiltFilesHook } from '@seedcord/utils/internal';\nimport { Envapter } from 'envapt';\n\nexport function setEnvaptBaseDir(): void {\n setBuiltFilesHook(() => {\n const configDir = botConfigDir();\n if (configDir !== undefined && Envapter.source.supportsFiles) Envapter.baseDir = configDir;\n });\n}\n","import { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\n\n// the edge bundle never reaches this entry\nassertDeclaredRuntime();\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#src/lifecycle/withTimeout';\nexport { shutdownOf } from '#node/ServerHost';\nexport { setEnvaptBaseDir } from '#node/envaptBaseDir';\n"],"mappings":"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"}
|
package/dist/node.index.d.mts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { n as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-H5YjZn8A.mjs";
|
|
2
|
+
import { t as ServerHost } from "./ServerHost-DYeh0KTk.mjs";
|
|
3
|
+
export { CoordinatedShutdown, CoordinatedStartup, ServerHost };
|