@wolfstar/cli 2.1.0 → 2.3.0-next-20261004131917

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 (79) hide show
  1. package/README.md +203 -42
  2. package/dist/{_shared-B-hRZDRS.js → _shared-BIQK0Bhm.js} +12 -5
  3. package/dist/_shared-BIQK0Bhm.js.map +1 -0
  4. package/dist/bridge-BvkmlAOZ.js +410 -0
  5. package/dist/bridge-BvkmlAOZ.js.map +1 -0
  6. package/dist/{build-CPnSLYME.js → build-DJcc95Yq.js} +5 -5
  7. package/dist/{build-CPnSLYME.js.map → build-DJcc95Yq.js.map} +1 -1
  8. package/dist/cli.js +1 -1
  9. package/dist/{codegen-BqpLGLLs.js → codegen-aJSyPz-J.js} +4 -4
  10. package/dist/{codegen-BqpLGLLs.js.map → codegen-aJSyPz-J.js.map} +1 -1
  11. package/dist/command-diff-BK912Hjc.js +78 -0
  12. package/dist/command-diff-BK912Hjc.js.map +1 -0
  13. package/dist/commands-CIPWQtF0.js +3 -0
  14. package/dist/commands-Clpbqq3K.js +15 -0
  15. package/dist/commands-Clpbqq3K.js.map +1 -0
  16. package/dist/{commands-ExM8HILu.js → commands-DVC7ky6f.js} +259 -9
  17. package/dist/commands-DVC7ky6f.js.map +1 -0
  18. package/dist/completions-D2KYtrrv.js +161 -0
  19. package/dist/completions-D2KYtrrv.js.map +1 -0
  20. package/dist/{dev-4wpEQC7l.js → dev-oPPVR7d_.js} +432 -45
  21. package/dist/dev-oPPVR7d_.js.map +1 -0
  22. package/dist/{diagnostics-CZBVxlpj.js → diagnostics-BysbNnPD.js} +38 -1
  23. package/dist/diagnostics-BysbNnPD.js.map +1 -0
  24. package/dist/doctor-DkVUwgDd.js +334 -0
  25. package/dist/doctor-DkVUwgDd.js.map +1 -0
  26. package/dist/{errors-Crt4hu0b.js → errors-COFWKKKw.js} +5 -2
  27. package/dist/errors-COFWKKKw.js.map +1 -0
  28. package/dist/hooks-Cy6F2bhH.js +332 -0
  29. package/dist/hooks-Cy6F2bhH.js.map +1 -0
  30. package/dist/index.d.ts +75 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -4
  33. package/dist/{info-CdiQajgn.js → info-CKJVMWIT.js} +16 -5
  34. package/dist/info-CKJVMWIT.js.map +1 -0
  35. package/dist/{locales-C7X8VNmK.js → locales-BJAP4HYf.js} +5 -5
  36. package/dist/{locales-C7X8VNmK.js.map → locales-BJAP4HYf.js.map} +1 -1
  37. package/dist/log-view-B7uJEnMR.js +342 -0
  38. package/dist/log-view-B7uJEnMR.js.map +1 -0
  39. package/dist/{nitro-sP2F3SQu.js → nitro-B8u1iVgf.js} +2 -2
  40. package/dist/{nitro-sP2F3SQu.js.map → nitro-B8u1iVgf.js.map} +1 -1
  41. package/dist/{plain-B6z85uQP.js → plain-DG7cy0Kk.js} +7 -2
  42. package/dist/plain-DG7cy0Kk.js.map +1 -0
  43. package/dist/{prepare-B4ysyG6H.js → prepare-BM93i3Rm.js} +9 -5
  44. package/dist/prepare-BM93i3Rm.js.map +1 -0
  45. package/dist/{project-MD4dIo2X.js → project-zlQStwsf.js} +2 -2
  46. package/dist/{project-MD4dIo2X.js.map → project-zlQStwsf.js.map} +1 -1
  47. package/dist/{run-Ddwrxe1O.js → run-DMM2YlP3.js} +3 -13
  48. package/dist/run-DMM2YlP3.js.map +1 -0
  49. package/dist/{theme-3Fc_LI-2.js → theme-yS2h5dd_.js} +25 -1
  50. package/dist/theme-yS2h5dd_.js.map +1 -0
  51. package/dist/tsc-C5l5TQGC.js +3 -0
  52. package/dist/{tsc-DO7D_uCl.js → tsc-puVAcwmo.js} +24 -5
  53. package/dist/tsc-puVAcwmo.js.map +1 -0
  54. package/dist/{tsdown-CD68G8_U.js → tsdown-Dwt3JhPw.js} +2 -2
  55. package/dist/{tsdown-CD68G8_U.js.map → tsdown-Dwt3JhPw.js.map} +1 -1
  56. package/dist/{tui-BRddrLQP.js → tui-nldGSD4c.js} +913 -141
  57. package/dist/tui-nldGSD4c.js.map +1 -0
  58. package/dist/{vite-CLyB6wl7.js → vite-YZUtv2Hk.js} +2 -2
  59. package/dist/{vite-CLyB6wl7.js.map → vite-YZUtv2Hk.js.map} +1 -1
  60. package/package.json +5 -4
  61. package/dist/_shared-B-hRZDRS.js.map +0 -1
  62. package/dist/commands-ExM8HILu.js.map +0 -1
  63. package/dist/dev-4wpEQC7l.js.map +0 -1
  64. package/dist/diagnostics-CZBVxlpj.js.map +0 -1
  65. package/dist/errors-Crt4hu0b.js.map +0 -1
  66. package/dist/hooks-H9kjLCMk.js +0 -148
  67. package/dist/hooks-H9kjLCMk.js.map +0 -1
  68. package/dist/info-CdiQajgn.js.map +0 -1
  69. package/dist/log-buffer-CY82aqnb.js +0 -72
  70. package/dist/log-buffer-CY82aqnb.js.map +0 -1
  71. package/dist/plain-B6z85uQP.js.map +0 -1
  72. package/dist/prepare-B4ysyG6H.js.map +0 -1
  73. package/dist/run-Ddwrxe1O.js.map +0 -1
  74. package/dist/theme-3Fc_LI-2.js.map +0 -1
  75. package/dist/tsc-DO7D_uCl.js.map +0 -1
  76. package/dist/tsc-YnAWKn3n.js +0 -3
  77. package/dist/tui-BRddrLQP.js.map +0 -1
  78. package/dist/tunnel-DT1FH6oA.js +0 -175
  79. package/dist/tunnel-DT1FH6oA.js.map +0 -1
@@ -0,0 +1,410 @@
1
+ import { t as findInstalledVersion } from "./project-zlQStwsf.js";
2
+ import { i as readProjectEnv } from "./hooks-Cy6F2bhH.js";
3
+ import { pathToFileURL } from "node:url";
4
+ import { EventEmitter } from "node:events";
5
+ import { resolveSync } from "mlly";
6
+ import { startTunnel } from "untun";
7
+
8
+ //#region src/dev/tunnel.ts
9
+ /**
10
+ * Exposes the bot's interactions endpoint publicly while `stars dev` runs.
11
+ *
12
+ * `dev.tunnel: true` opens a `cloudflared` quick tunnel via `untun` (it changes on every run); a configured https
13
+ * URL is only probed, since the user already serves it. Writing the URL to the Discord application is opt-in
14
+ * through `dev.tunnel.updateEndpoint`, because it edits a live application.
15
+ */
16
+ var Tunnel = class extends EventEmitter {
17
+ config;
18
+ #startTunnel;
19
+ #tunnel = null;
20
+ #state = "off";
21
+ #url = null;
22
+ #wanted = false;
23
+ constructor(config, options = {}) {
24
+ super();
25
+ this.config = config;
26
+ this.#startTunnel = options.startTunnel ?? startTunnel;
27
+ }
28
+ get state() {
29
+ return this.#state;
30
+ }
31
+ /** The public URL of the interactions endpoint, `null` until the tunnel is up. */
32
+ get url() {
33
+ return this.#url;
34
+ }
35
+ async start(forceQuick = false) {
36
+ if (this.#state === "starting" || this.#state === "up") return;
37
+ const configured = this.config.dev.tunnel;
38
+ const tunnel = forceQuick && configured.mode === "off" ? {
39
+ mode: "quick",
40
+ path: "/",
41
+ updateEndpoint: false
42
+ } : configured;
43
+ if (tunnel.mode === "off") return;
44
+ this.#wanted = true;
45
+ this.#setState("starting", null);
46
+ const url = tunnel.mode === "url" ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();
47
+ if (!url || !this.#wanted) return;
48
+ this.#setState("up", endpointUrl(url, tunnel.path));
49
+ this.emit("log", "success", `Tunnel ready at ${this.#url}`);
50
+ if (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url);
51
+ }
52
+ /** Toggles the configured tunnel, opening a quick tunnel when the project did not configure one. */
53
+ toggle() {
54
+ return this.#state === "starting" || this.#state === "up" ? this.close() : this.start(true);
55
+ }
56
+ async close() {
57
+ this.#wanted = false;
58
+ const tunnel = this.#tunnel;
59
+ this.#tunnel = null;
60
+ this.#setState("off", null);
61
+ if (tunnel) await tunnel.close();
62
+ }
63
+ async #useConfiguredUrl(tunnel) {
64
+ if (!await probe(endpointUrl(tunnel.url, tunnel.path))) this.emit("log", "warn", `${tunnel.url} did not answer; make sure it forwards to ${this.config.dev.url ?? "the bot"}`);
65
+ return tunnel.url;
66
+ }
67
+ async #openQuickTunnel() {
68
+ const target = this.config.dev.url;
69
+ if (!target) {
70
+ this.emit("log", "error", "A quick tunnel needs `dev.url` to know what to forward to");
71
+ this.#setState("failed", null);
72
+ return null;
73
+ }
74
+ this.emit("log", "info", "Opening a cloudflared quick tunnel…");
75
+ try {
76
+ const tunnel = await this.#startTunnel({
77
+ url: target,
78
+ acceptCloudflareNotice: true
79
+ });
80
+ if (!this.#wanted) {
81
+ await tunnel?.close();
82
+ return null;
83
+ }
84
+ if (!tunnel) {
85
+ this.emit("log", "error", "cloudflared setup was cancelled");
86
+ this.#setState("failed", null);
87
+ return null;
88
+ }
89
+ this.#tunnel = tunnel;
90
+ return await tunnel.getURL();
91
+ } catch (error) {
92
+ this.emit("log", "error", `cloudflared failed: ${error instanceof Error ? error.message : String(error)}`);
93
+ this.#setState("failed", null);
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a
99
+ * signed ping before accepting it, so the bot has to be up already — this runs after the first successful build.
100
+ */
101
+ async #updateInteractionsEndpoint(url) {
102
+ const credentials = readDiscordCredentials(this.config);
103
+ if (!credentials) {
104
+ this.emit("log", "warn", "Skipped the interactions endpoint update: DISCORD_TOKEN is not set");
105
+ return;
106
+ }
107
+ try {
108
+ const response = await fetch("https://discord.com/api/v10/applications/@me", {
109
+ method: "PATCH",
110
+ headers: {
111
+ authorization: `Bot ${credentials.token}`,
112
+ "content-type": "application/json"
113
+ },
114
+ body: JSON.stringify({ interactions_endpoint_url: url }),
115
+ signal: AbortSignal.timeout(1e4)
116
+ });
117
+ if (response.ok) this.emit("log", "success", `Interactions endpoint set to ${url}`);
118
+ else this.emit("log", "error", `Discord rejected the interactions endpoint (${response.status}): ${(await response.text()).slice(0, 300)}`);
119
+ } catch (error) {
120
+ this.emit("log", "error", `Failed to update the interactions endpoint: ${error instanceof Error ? error.message : String(error)}`);
121
+ }
122
+ }
123
+ #setState(state, url) {
124
+ this.#state = state;
125
+ this.#url = url;
126
+ this.emit("state", state, url);
127
+ }
128
+ };
129
+ /**
130
+ * Joins the tunnel's origin with the path the interactions endpoint is served on.
131
+ */
132
+ function endpointUrl(origin, path) {
133
+ return path === "/" ? origin : new URL(path, origin.endsWith("/") ? origin : `${origin}/`).href;
134
+ }
135
+ async function probe(url) {
136
+ try {
137
+ return (await fetch(url, {
138
+ method: "HEAD",
139
+ signal: AbortSignal.timeout(5e3)
140
+ })).status < 500;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ /**
146
+ * Reads the Discord credentials the CLI needs from the environment, falling back to the project's `.env` files the
147
+ * way the bot itself does once it starts.
148
+ */
149
+ function readDiscordCredentials(config, env = process.env) {
150
+ const fromFiles = readProjectEnvFilesCached(config);
151
+ const read = (...keys) => {
152
+ for (const key of keys) {
153
+ const value = config.dev.env[key] ?? env[key] ?? fromFiles[key];
154
+ if (value) return value;
155
+ }
156
+ return null;
157
+ };
158
+ const token = read("DISCORD_TOKEN", "TOKEN");
159
+ if (!token) return null;
160
+ return {
161
+ token,
162
+ applicationId: read("DISCORD_APPLICATION_ID", "APPLICATION_ID", "DISCORD_CLIENT_ID", "CLIENT_ID")
163
+ };
164
+ }
165
+ let envFileCache = null;
166
+ /** The variables the bot loads (see {@link readProjectEnv}), once per project and `env` options. */
167
+ function readProjectEnvFilesCached(config) {
168
+ const key = JSON.stringify([config.root, config.env.options]);
169
+ if (envFileCache?.key !== key) envFileCache = {
170
+ key,
171
+ values: readProjectEnv(config)
172
+ };
173
+ return envFileCache.values;
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/dev/bridge.ts
178
+ /** Marks a message of the bridge, so a bot using the IPC channel for something else is left alone. */
179
+ const BRIDGE_SOURCE = "stars:bridge";
180
+ /** Marks a message `stars dev` sends to the bot. */
181
+ const CLI_SOURCE = "stars:cli";
182
+ /** Set on a bot started only to report the commands it registers (see `readLocalCommands`). */
183
+ const DUMP_ENV = "STARS_COMMANDS_DUMP";
184
+ /** The first `@wolfstar/http-framework` with object plugins (`Client.use({ name, postListen })`), which the bridge is. */
185
+ const BRIDGE_FRAMEWORK_VERSION = "6.1.0";
186
+ const isString = (value) => typeof value === "string";
187
+ const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
188
+ const isStrings = (value) => Array.isArray(value) && value.every(isString);
189
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
190
+ const isCommands = (value) => Array.isArray(value) && value.every((command) => isRecord(command) && isString(command.name));
191
+ const isError = (value) => isString(value.message) && isStrings(value.stack);
192
+ /** The shape each message must have. A plugin can send `log`, so nothing here is taken on trust. */
193
+ const SHAPES = {
194
+ ready: (message) => isString(message.clientId) && (message.port === null || isNumber(message.port)),
195
+ pieces: (message) => isString(message.store) && Array.isArray(message.pieces) && message.pieces.every((piece) => isRecord(piece) && isString(piece.name) && isString(piece.path)),
196
+ hmr: (message) => {
197
+ switch (message.event) {
198
+ case "start": return isStrings(message.paths);
199
+ case "stop": return true;
200
+ case "error": return isString(message.path) && isError(message);
201
+ case "reloaded":
202
+ case "loaded":
203
+ case "unloaded": return isString(message.store) && isStrings(message.names) && isString(message.path);
204
+ default: return false;
205
+ }
206
+ },
207
+ commands: (message) => isCommands(message.global) && isRecord(message.guilds) && Object.values(message.guilds).every(isCommands),
208
+ dispatch: (message) => {
209
+ if (!isString(message.route) || !isString(message.piece)) return false;
210
+ if (message.phase === "run") return true;
211
+ if (message.ms !== null && !isNumber(message.ms)) return false;
212
+ return message.phase === "success" || message.phase === "error" && isRecord(message.error) && isError(message.error);
213
+ },
214
+ request: (message) => isString(message.method) && isString(message.path) && isNumber(message.status) && isNumber(message.ms),
215
+ refreshed: (message) => message.ok === true ? isNumber(message.global) && isNumber(message.guilds) && isNumber(message.cleared) : message.ok === false && isError(message),
216
+ log: (message) => isString(message.channel) && message.channel.length > 0 && isString(message.level) && isString(message.text) && (message.detail === void 0 || isStrings(message.detail))
217
+ };
218
+ /**
219
+ * Narrows an IPC message to one of the bridge's, `null` for anything else the bot sends and for a bridge message that
220
+ * does not have the shape of its type.
221
+ */
222
+ function parseBridgeMessage(value) {
223
+ if (!isRecord(value) || value.source !== "stars:bridge" || !isString(value.type) || !Object.hasOwn(SHAPES, value.type)) return null;
224
+ return SHAPES[value.type](value) ? value : null;
225
+ }
226
+ /**
227
+ * The source of the bridge: a module preloaded into the bot (`node --import`) that registers a plugin on the bot's own
228
+ * `Client` and reports what happens there with `process.send`. It never writes to stdout, and does nothing when the
229
+ * process has no IPC channel, so it is inert outside `stars dev`.
230
+ *
231
+ * It only uses what `@wolfstar/http-framework` already exposes — the client's events, its registry and its stores —
232
+ * which is what keeps `@wolfstar/cli` free of a dependency on the framework: `framework` is the project's own copy.
233
+ */
234
+ function bridgeModuleSource(framework) {
235
+ return `import { Client, container } from ${JSON.stringify(framework)};
236
+
237
+ if (typeof process.send === 'function') {
238
+ const send = (type, data) => {
239
+ try {
240
+ process.send({ source: ${JSON.stringify(BRIDGE_SOURCE)}, type, ...data });
241
+ } catch {}
242
+ };
243
+ const describe = (error) => ({
244
+ message: error instanceof Error ? error.message : String(error),
245
+ stack:
246
+ error instanceof Error && typeof error.stack === 'string'
247
+ ? error.stack.split('\\n').slice(1, 6).map((line) => line.trim())
248
+ : []
249
+ });
250
+ const commandRoute = (context) => {
251
+ const data = context.interaction.data ?? {};
252
+ const kind = context.interaction.type === 4 ? 'autocomplete' : data.type === 2 ? 'user' : data.type === 3 ? 'message' : 'slash';
253
+ let name = String(data.name ?? 'unknown');
254
+ let options = data.options;
255
+ while (Array.isArray(options) && options.length === 1 && (options[0].type === 1 || options[0].type === 2)) {
256
+ name += ' ' + options[0].name;
257
+ options = options[0].options;
258
+ }
259
+ return kind + ':' + name;
260
+ };
261
+ const handlerRoute = (context) => (context.interaction.type === 5 ? 'modal' : 'component') + ':' + context.handler.name;
262
+ const commandsOf = (client) => {
263
+ const guilds = {};
264
+ for (const [id, commands] of client.registry.getLoadedGuildCommands()) guilds[id] = commands;
265
+ return { global: client.registry.getLoadedGlobalCommands(), guilds };
266
+ };
267
+ const snapshot = (client) => {
268
+ try {
269
+ send('commands', commandsOf(client));
270
+ } catch {}
271
+ };
272
+
273
+ try {
274
+ Client.use({
275
+ name: 'stars:dev-bridge',
276
+ enforce: 'post',
277
+ postInitialization(client) {
278
+ if (process.env[${JSON.stringify(DUMP_ENV)}] === '1') {
279
+ // \`stars commands diff\` only wants to know what the bot would register: once its pieces are loaded
280
+ // it has the answer, and exits before it listens or talks to Discord.
281
+ const load = client.load.bind(client);
282
+ client.load = async (...args) => {
283
+ await load(...args);
284
+ process.send({ source: ${JSON.stringify(BRIDGE_SOURCE)}, type: 'commands', ...commandsOf(client) }, () => process.exit(0));
285
+ await new Promise(() => {});
286
+ };
287
+ return;
288
+ }
289
+
290
+ const started = new WeakMap();
291
+ const track = (prefix, route, pieceOf) => {
292
+ client.on(prefix + 'Run', (context) => {
293
+ started.set(context.interaction, performance.now());
294
+ send('dispatch', { phase: 'run', route: route(context), piece: pieceOf(context).name });
295
+ });
296
+ const done = (phase) => (first, second) => {
297
+ const context = phase === 'error' ? second : first;
298
+ const at = started.get(context.interaction);
299
+ send('dispatch', {
300
+ phase,
301
+ route: route(context),
302
+ piece: pieceOf(context).name,
303
+ ms: at === undefined ? null : Math.round(performance.now() - at),
304
+ ...(phase === 'error' ? { error: describe(first) } : {})
305
+ });
306
+ };
307
+ client.on(prefix + 'Success', done('success'));
308
+ client.on(prefix + 'Error', done('error'));
309
+ };
310
+ track('command', commandRoute, (context) => context.command);
311
+ track('autocomplete', commandRoute, (context) => context.command);
312
+ track('interactionHandler', handlerRoute, (context) => context.handler);
313
+
314
+ const changed = (event) => (pieces, path) => {
315
+ const list = Array.isArray(pieces) ? pieces : [pieces];
316
+ const store = list[0]?.store?.name ?? 'pieces';
317
+ send('hmr', { event, path, store, names: list.map((piece) => piece.name) });
318
+ if (store === 'commands') snapshot(client);
319
+ };
320
+ client.on('hmrStart', (paths) => send('hmr', { event: 'start', paths }));
321
+ client.on('hmrStop', () => send('hmr', { event: 'stop' }));
322
+ client.on('hmrPieceReloaded', changed('reloaded'));
323
+ client.on('hmrPiecesLoaded', changed('loaded'));
324
+ client.on('hmrPieceUnloaded', changed('unloaded'));
325
+ client.on('hmrError', (error, path) => send('hmr', { event: 'error', path, ...describe(error) }));
326
+ },
327
+ postListen(client) {
328
+ if (process.env[${JSON.stringify(DUMP_ENV)}] === '1') return;
329
+ const server = client.server;
330
+ server?.on?.('request', (request, response) => {
331
+ const at = performance.now();
332
+ response.once('finish', () =>
333
+ send('request', {
334
+ method: String(request.method),
335
+ path: String(request.url),
336
+ status: response.statusCode,
337
+ ms: Math.round(performance.now() - at)
338
+ })
339
+ );
340
+ });
341
+ for (const store of container.stores.values()) {
342
+ send('pieces', {
343
+ store: store.name,
344
+ pieces: [...store.values()].map((piece) => ({ name: piece.name, path: piece.location.full }))
345
+ });
346
+ }
347
+ snapshot(client);
348
+ const address = server?.address?.();
349
+ send('ready', { clientId: client.id, port: address !== null && typeof address === 'object' ? address.port : null });
350
+ }
351
+ });
352
+
353
+ process.on('message', (message) => {
354
+ if (message === null || typeof message !== 'object' || message.source !== ${JSON.stringify(CLI_SOURCE)}) return;
355
+ if (message.type !== 'commands:refresh') return;
356
+ Promise.resolve()
357
+ .then(async () => {
358
+ const client = container.client;
359
+ const global = await client.registry.pushGlobalCommands();
360
+ const guilds = await client.registry.pushGuildRestrictedCommands();
361
+ const failed = guilds.find((result) => result.status === 'rejected');
362
+ if (failed) throw failed.reason;
363
+ // A guild that lost its last command is no longer in the registry, so nothing above overwrote it.
364
+ const clear = Array.isArray(message.clearGuilds) ? message.clearGuilds.filter((id) => typeof id === 'string') : [];
365
+ for (const id of clear) {
366
+ await container.rest.put('/applications/' + client.id + '/guilds/' + id + '/commands', { body: [] });
367
+ }
368
+ send('refreshed', { ok: true, global: global.length, guilds: guilds.length, cleared: clear.length });
369
+ })
370
+ .catch((error) => send('refreshed', { ok: false, ...describe(error) }));
371
+ });
372
+ // Listening for messages keeps the channel, and with it the process, alive: a bot that is done must still exit.
373
+ process.channel?.unref?.();
374
+ } catch {}
375
+ }
376
+ `;
377
+ }
378
+ /**
379
+ * `node` arguments that preload the bridge into the bot. Nothing is preloaded when the project's framework cannot be
380
+ * resolved or predates object plugins: `stars dev` then shows what the bot prints, as it did before the bridge.
381
+ *
382
+ * The framework is resolved from the project with the `import` condition, the way `moduleImportArgs` does, so the
383
+ * plugin lands on the `Client` the bot constructs. A build that bundles the framework into its output (Vite, Nitro)
384
+ * has its own copy, which the preload cannot reach: the bridge stays silent there too.
385
+ */
386
+ function bridgeImportArgs(config) {
387
+ const version = findInstalledVersion(config.root, "@wolfstar/http-framework");
388
+ if (version === null || !isAtLeast(version, "6.1.0")) return [];
389
+ let framework;
390
+ try {
391
+ framework = resolveSync("@wolfstar/http-framework", { url: pathToFileURL(`${config.root}/package.json`).href });
392
+ } catch {
393
+ return [];
394
+ }
395
+ return ["--import", `data:text/javascript,${encodeURIComponent(bridgeModuleSource(framework))}`];
396
+ }
397
+ /** Compares the `major.minor.patch` of two versions; a prerelease counts as its release. */
398
+ function isAtLeast(version, minimum) {
399
+ const parse = (value) => value.split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
400
+ const [a, b] = [parse(version), parse(minimum)];
401
+ for (let index = 0; index < 3; index++) {
402
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
403
+ if (difference !== 0) return difference > 0;
404
+ }
405
+ return true;
406
+ }
407
+
408
+ //#endregion
409
+ export { isAtLeast as a, endpointUrl as c, bridgeImportArgs as i, readDiscordCredentials as l, CLI_SOURCE as n, parseBridgeMessage as o, DUMP_ENV as r, Tunnel as s, BRIDGE_FRAMEWORK_VERSION as t };
410
+ //# sourceMappingURL=bridge-BvkmlAOZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-BvkmlAOZ.js","names":[],"sources":["../src/dev/tunnel.ts","../src/dev/bridge.ts"],"sourcesContent":["import type { ResolvedStarsConfig, ResolvedTunnelConfig } from '@wolfstar/schema';\nimport { readProjectEnv } from '../utils/project-env.js';\nimport { EventEmitter } from 'node:events';\nimport { startTunnel, type Tunnel as UntunTunnel } from 'untun';\nimport type { LogLevel } from '../utils/log-buffer.js';\n\nexport type TunnelState = 'off' | 'starting' | 'up' | 'failed';\n\nexport interface TunnelEvents {\n\tlog: [level: LogLevel, text: string];\n\tstate: [state: TunnelState, url: string | null];\n}\n\nexport interface TunnelOptions {\n\t/** Override for tests. */\n\tstartTunnel?: typeof startTunnel;\n}\n\n/**\n * Exposes the bot's interactions endpoint publicly while `stars dev` runs.\n *\n * `dev.tunnel: true` opens a `cloudflared` quick tunnel via `untun` (it changes on every run); a configured https\n * URL is only probed, since the user already serves it. Writing the URL to the Discord application is opt-in\n * through `dev.tunnel.updateEndpoint`, because it edits a live application.\n */\nexport class Tunnel extends EventEmitter<TunnelEvents> {\n\t#startTunnel: typeof startTunnel;\n\t#tunnel: UntunTunnel | null = null;\n\t#state: TunnelState = 'off';\n\t#url: string | null = null;\n\t#wanted = false;\n\n\tpublic constructor(\n\t\tprivate readonly config: ResolvedStarsConfig,\n\t\toptions: TunnelOptions = {}\n\t) {\n\t\tsuper();\n\t\tthis.#startTunnel = options.startTunnel ?? startTunnel;\n\t}\n\n\tpublic get state(): TunnelState {\n\t\treturn this.#state;\n\t}\n\n\t/** The public URL of the interactions endpoint, `null` until the tunnel is up. */\n\tpublic get url(): string | null {\n\t\treturn this.#url;\n\t}\n\n\tpublic async start(forceQuick = false): Promise<void> {\n\t\tif (this.#state === 'starting' || this.#state === 'up') return;\n\t\tconst configured = this.config.dev.tunnel;\n\t\tconst tunnel: ResolvedTunnelConfig =\n\t\t\tforceQuick && configured.mode === 'off' ? { mode: 'quick', path: '/', updateEndpoint: false } : configured;\n\t\tif (tunnel.mode === 'off') return;\n\n\t\tthis.#wanted = true;\n\t\tthis.#setState('starting', null);\n\t\tconst url = tunnel.mode === 'url' ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();\n\t\tif (!url || !this.#wanted) return;\n\n\t\tthis.#setState('up', endpointUrl(url, tunnel.path));\n\t\tthis.emit('log', 'success', `Tunnel ready at ${this.#url}`);\n\t\tif (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url!);\n\t}\n\n\t/** Toggles the configured tunnel, opening a quick tunnel when the project did not configure one. */\n\tpublic toggle(): Promise<void> {\n\t\treturn this.#state === 'starting' || this.#state === 'up' ? this.close() : this.start(true);\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tthis.#wanted = false;\n\t\tconst tunnel = this.#tunnel;\n\t\tthis.#tunnel = null;\n\t\tthis.#setState('off', null);\n\t\tif (tunnel) await tunnel.close();\n\t}\n\n\tasync #useConfiguredUrl(tunnel: Extract<ResolvedTunnelConfig, { mode: 'url' }>): Promise<string | null> {\n\t\tconst reachable = await probe(endpointUrl(tunnel.url, tunnel.path));\n\t\tif (!reachable) {\n\t\t\tthis.emit('log', 'warn', `${tunnel.url} did not answer; make sure it forwards to ${this.config.dev.url ?? 'the bot'}`);\n\t\t}\n\n\t\treturn tunnel.url;\n\t}\n\n\tasync #openQuickTunnel(): Promise<string | null> {\n\t\tconst target = this.config.dev.url;\n\t\tif (!target) {\n\t\t\tthis.emit('log', 'error', 'A quick tunnel needs `dev.url` to know what to forward to');\n\t\t\tthis.#setState('failed', null);\n\t\t\treturn null;\n\t\t}\n\n\t\tthis.emit('log', 'info', 'Opening a cloudflared quick tunnel…');\n\n\t\ttry {\n\t\t\tconst tunnel = await this.#startTunnel({ url: target, acceptCloudflareNotice: true });\n\t\t\tif (!this.#wanted) {\n\t\t\t\tawait tunnel?.close();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (!tunnel) {\n\t\t\t\tthis.emit('log', 'error', 'cloudflared setup was cancelled');\n\t\t\t\tthis.#setState('failed', null);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tthis.#tunnel = tunnel;\n\t\t\treturn await tunnel.getURL();\n\t\t} catch (error) {\n\t\t\tthis.emit('log', 'error', `cloudflared failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\tthis.#setState('failed', null);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a\n\t * signed ping before accepting it, so the bot has to be up already — this runs after the first successful build.\n\t */\n\tasync #updateInteractionsEndpoint(url: string): Promise<void> {\n\t\tconst credentials = readDiscordCredentials(this.config);\n\t\tif (!credentials) {\n\t\t\tthis.emit('log', 'warn', 'Skipped the interactions endpoint update: DISCORD_TOKEN is not set');\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch('https://discord.com/api/v10/applications/@me', {\n\t\t\t\tmethod: 'PATCH',\n\t\t\t\theaders: { authorization: `Bot ${credentials.token}`, 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ interactions_endpoint_url: url }),\n\t\t\t\tsignal: AbortSignal.timeout(10_000)\n\t\t\t});\n\n\t\t\tif (response.ok) this.emit('log', 'success', `Interactions endpoint set to ${url}`);\n\t\t\telse\n\t\t\t\tthis.emit(\n\t\t\t\t\t'log',\n\t\t\t\t\t'error',\n\t\t\t\t\t`Discord rejected the interactions endpoint (${response.status}): ${(await response.text()).slice(0, 300)}`\n\t\t\t\t);\n\t\t} catch (error) {\n\t\t\tthis.emit('log', 'error', `Failed to update the interactions endpoint: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t#setState(state: TunnelState, url: string | null): void {\n\t\tthis.#state = state;\n\t\tthis.#url = url;\n\t\tthis.emit('state', state, url);\n\t}\n}\n\n/**\n * Joins the tunnel's origin with the path the interactions endpoint is served on.\n */\nexport function endpointUrl(origin: string, path: string): string {\n\treturn path === '/' ? origin : new URL(path, origin.endsWith('/') ? origin : `${origin}/`).href;\n}\n\nasync function probe(url: string): Promise<boolean> {\n\ttry {\n\t\tconst response = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) });\n\t\treturn response.status < 500;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport interface DiscordCredentials {\n\ttoken: string;\n\tapplicationId: string | null;\n}\n\n/**\n * Reads the Discord credentials the CLI needs from the environment, falling back to the project's `.env` files the\n * way the bot itself does once it starts.\n */\nexport function readDiscordCredentials(config: ResolvedStarsConfig, env: NodeJS.ProcessEnv = process.env): DiscordCredentials | null {\n\tconst fromFiles = readProjectEnvFilesCached(config);\n\tconst read = (...keys: string[]): string | null => {\n\t\tfor (const key of keys) {\n\t\t\tconst value = config.dev.env[key] ?? env[key] ?? fromFiles[key];\n\t\t\tif (value) return value;\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst token = read('DISCORD_TOKEN', 'TOKEN');\n\tif (!token) return null;\n\n\treturn { token, applicationId: read('DISCORD_APPLICATION_ID', 'APPLICATION_ID', 'DISCORD_CLIENT_ID', 'CLIENT_ID') };\n}\n\nlet envFileCache: { key: string; values: Record<string, string> } | null = null;\n\n/** The variables the bot loads (see {@link readProjectEnv}), once per project and `env` options. */\nfunction readProjectEnvFilesCached(config: ResolvedStarsConfig): Record<string, string> {\n\tconst key = JSON.stringify([config.root, config.env.options]);\n\tif (envFileCache?.key !== key) envFileCache = { key, values: readProjectEnv(config) };\n\treturn envFileCache.values;\n}\n","import type { ResolvedStarsConfig } from '@wolfstar/schema';\nimport { resolveSync } from 'mlly';\nimport { pathToFileURL } from 'node:url';\nimport { findInstalledVersion } from '../utils/project.js';\n\n/** Marks a message of the bridge, so a bot using the IPC channel for something else is left alone. */\nexport const BRIDGE_SOURCE = 'stars:bridge';\n/** Marks a message `stars dev` sends to the bot. */\nexport const CLI_SOURCE = 'stars:cli';\n/** Set on a bot started only to report the commands it registers (see `readLocalCommands`). */\nexport const DUMP_ENV = 'STARS_COMMANDS_DUMP';\n/** The first `@wolfstar/http-framework` with object plugins (`Client.use({ name, postListen })`), which the bridge is. */\nexport const BRIDGE_FRAMEWORK_VERSION = '6.1.0';\n\n/** An application command as the bot would register it: the JSON body Discord receives. */\nexport interface CommandData {\n\tname: string;\n\ttype?: number;\n\t[key: string]: unknown;\n}\n\nexport interface CommandSnapshot {\n\tglobal: CommandData[];\n\t/** Guild-restricted commands, by guild id. */\n\tguilds: Record<string, CommandData[]>;\n}\n\nexport interface BridgeError {\n\tmessage: string;\n\t/** The first stack frames, without the message line. */\n\tstack: string[];\n}\n\n/**\n * What the bot tells `stars dev` over the IPC channel. The bot's stdout only carries text a level is guessed for; this\n * is everything the dev UI shows that happens inside the bot: hot reloads, requests, dispatches, its commands.\n */\nexport type BridgeMessage =\n\t| { type: 'ready'; clientId: string; port: number | null }\n\t| { type: 'pieces'; store: string; pieces: { name: string; path: string }[] }\n\t| { type: 'hmr'; event: 'start'; paths: string[] }\n\t| { type: 'hmr'; event: 'stop' }\n\t| { type: 'hmr'; event: 'reloaded' | 'loaded' | 'unloaded'; store: string; names: string[]; path: string }\n\t| ({ type: 'hmr'; event: 'error'; path: string } & BridgeError)\n\t| ({ type: 'commands' } & CommandSnapshot)\n\t| { type: 'dispatch'; phase: 'run'; route: string; piece: string }\n\t| { type: 'dispatch'; phase: 'success'; route: string; piece: string; ms: number | null }\n\t| { type: 'dispatch'; phase: 'error'; route: string; piece: string; ms: number | null; error: BridgeError }\n\t| { type: 'request'; method: string; path: string; status: number; ms: number }\n\t| { type: 'refreshed'; ok: true; global: number; guilds: number; cleared: number }\n\t| ({ type: 'refreshed'; ok: false } & BridgeError)\n\t/** A line on a channel of the sender's choice: how a plugin gets its own channel in the dev UI. */\n\t| { type: 'log'; channel: string; level: string; text: string; detail?: string[] };\n\n/** What the CLI asks of the bot. `clearGuilds` are guilds whose last command was removed: nothing registers them any more. */\nexport interface RefreshRequest {\n\tsource: typeof CLI_SOURCE;\n\ttype: 'commands:refresh';\n\tclearGuilds: string[];\n}\n\ntype Loose = Record<string, any>;\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\nconst isNumber = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value);\nconst isStrings = (value: unknown): value is string[] => Array.isArray(value) && value.every(isString);\nconst isRecord = (value: unknown): value is Loose => value !== null && typeof value === 'object' && !Array.isArray(value);\nconst isCommands = (value: unknown): boolean => Array.isArray(value) && value.every((command) => isRecord(command) && isString(command.name));\nconst isError = (value: Loose): boolean => isString(value.message) && isStrings(value.stack);\n\n/** The shape each message must have. A plugin can send `log`, so nothing here is taken on trust. */\nconst SHAPES: Record<BridgeMessage['type'], (message: Loose) => boolean> = {\n\tready: (message) => isString(message.clientId) && (message.port === null || isNumber(message.port)),\n\tpieces: (message) =>\n\t\tisString(message.store) &&\n\t\tArray.isArray(message.pieces) &&\n\t\tmessage.pieces.every((piece: unknown) => isRecord(piece) && isString(piece.name) && isString(piece.path)),\n\thmr: (message) => {\n\t\tswitch (message.event) {\n\t\t\tcase 'start':\n\t\t\t\treturn isStrings(message.paths);\n\t\t\tcase 'stop':\n\t\t\t\treturn true;\n\t\t\tcase 'error':\n\t\t\t\treturn isString(message.path) && isError(message);\n\t\t\tcase 'reloaded':\n\t\t\tcase 'loaded':\n\t\t\tcase 'unloaded':\n\t\t\t\treturn isString(message.store) && isStrings(message.names) && isString(message.path);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t},\n\tcommands: (message) => isCommands(message.global) && isRecord(message.guilds) && Object.values(message.guilds).every(isCommands),\n\tdispatch: (message) => {\n\t\tif (!isString(message.route) || !isString(message.piece)) return false;\n\t\tif (message.phase === 'run') return true;\n\t\tif (message.ms !== null && !isNumber(message.ms)) return false;\n\t\treturn message.phase === 'success' || (message.phase === 'error' && isRecord(message.error) && isError(message.error));\n\t},\n\trequest: (message) => isString(message.method) && isString(message.path) && isNumber(message.status) && isNumber(message.ms),\n\trefreshed: (message) =>\n\t\tmessage.ok === true\n\t\t\t? isNumber(message.global) && isNumber(message.guilds) && isNumber(message.cleared)\n\t\t\t: message.ok === false && isError(message),\n\tlog: (message) =>\n\t\tisString(message.channel) &&\n\t\tmessage.channel.length > 0 &&\n\t\tisString(message.level) &&\n\t\tisString(message.text) &&\n\t\t(message.detail === undefined || isStrings(message.detail))\n};\n\n/**\n * Narrows an IPC message to one of the bridge's, `null` for anything else the bot sends and for a bridge message that\n * does not have the shape of its type.\n */\nexport function parseBridgeMessage(value: unknown): BridgeMessage | null {\n\tif (!isRecord(value) || value.source !== BRIDGE_SOURCE || !isString(value.type) || !Object.hasOwn(SHAPES, value.type)) return null;\n\treturn SHAPES[value.type as BridgeMessage['type']](value) ? (value as BridgeMessage) : null;\n}\n\n/**\n * The source of the bridge: a module preloaded into the bot (`node --import`) that registers a plugin on the bot's own\n * `Client` and reports what happens there with `process.send`. It never writes to stdout, and does nothing when the\n * process has no IPC channel, so it is inert outside `stars dev`.\n *\n * It only uses what `@wolfstar/http-framework` already exposes — the client's events, its registry and its stores —\n * which is what keeps `@wolfstar/cli` free of a dependency on the framework: `framework` is the project's own copy.\n */\nexport function bridgeModuleSource(framework: string): string {\n\treturn `import { Client, container } from ${JSON.stringify(framework)};\n\nif (typeof process.send === 'function') {\n\tconst send = (type, data) => {\n\t\ttry {\n\t\t\tprocess.send({ source: ${JSON.stringify(BRIDGE_SOURCE)}, type, ...data });\n\t\t} catch {}\n\t};\n\tconst describe = (error) => ({\n\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\tstack:\n\t\t\terror instanceof Error && typeof error.stack === 'string'\n\t\t\t\t? error.stack.split('\\\\n').slice(1, 6).map((line) => line.trim())\n\t\t\t\t: []\n\t});\n\tconst commandRoute = (context) => {\n\t\tconst data = context.interaction.data ?? {};\n\t\tconst kind = context.interaction.type === 4 ? 'autocomplete' : data.type === 2 ? 'user' : data.type === 3 ? 'message' : 'slash';\n\t\tlet name = String(data.name ?? 'unknown');\n\t\tlet options = data.options;\n\t\twhile (Array.isArray(options) && options.length === 1 && (options[0].type === 1 || options[0].type === 2)) {\n\t\t\tname += ' ' + options[0].name;\n\t\t\toptions = options[0].options;\n\t\t}\n\t\treturn kind + ':' + name;\n\t};\n\tconst handlerRoute = (context) => (context.interaction.type === 5 ? 'modal' : 'component') + ':' + context.handler.name;\n\tconst commandsOf = (client) => {\n\t\tconst guilds = {};\n\t\tfor (const [id, commands] of client.registry.getLoadedGuildCommands()) guilds[id] = commands;\n\t\treturn { global: client.registry.getLoadedGlobalCommands(), guilds };\n\t};\n\tconst snapshot = (client) => {\n\t\ttry {\n\t\t\tsend('commands', commandsOf(client));\n\t\t} catch {}\n\t};\n\n\ttry {\n\t\tClient.use({\n\t\t\tname: 'stars:dev-bridge',\n\t\t\tenforce: 'post',\n\t\t\tpostInitialization(client) {\n\t\t\t\tif (process.env[${JSON.stringify(DUMP_ENV)}] === '1') {\n\t\t\t\t\t// \\`stars commands diff\\` only wants to know what the bot would register: once its pieces are loaded\n\t\t\t\t\t// it has the answer, and exits before it listens or talks to Discord.\n\t\t\t\t\tconst load = client.load.bind(client);\n\t\t\t\t\tclient.load = async (...args) => {\n\t\t\t\t\t\tawait load(...args);\n\t\t\t\t\t\tprocess.send({ source: ${JSON.stringify(BRIDGE_SOURCE)}, type: 'commands', ...commandsOf(client) }, () => process.exit(0));\n\t\t\t\t\t\tawait new Promise(() => {});\n\t\t\t\t\t};\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst started = new WeakMap();\n\t\t\t\tconst track = (prefix, route, pieceOf) => {\n\t\t\t\t\tclient.on(prefix + 'Run', (context) => {\n\t\t\t\t\t\tstarted.set(context.interaction, performance.now());\n\t\t\t\t\t\tsend('dispatch', { phase: 'run', route: route(context), piece: pieceOf(context).name });\n\t\t\t\t\t});\n\t\t\t\t\tconst done = (phase) => (first, second) => {\n\t\t\t\t\t\tconst context = phase === 'error' ? second : first;\n\t\t\t\t\t\tconst at = started.get(context.interaction);\n\t\t\t\t\t\tsend('dispatch', {\n\t\t\t\t\t\t\tphase,\n\t\t\t\t\t\t\troute: route(context),\n\t\t\t\t\t\t\tpiece: pieceOf(context).name,\n\t\t\t\t\t\t\tms: at === undefined ? null : Math.round(performance.now() - at),\n\t\t\t\t\t\t\t...(phase === 'error' ? { error: describe(first) } : {})\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t\tclient.on(prefix + 'Success', done('success'));\n\t\t\t\t\tclient.on(prefix + 'Error', done('error'));\n\t\t\t\t};\n\t\t\t\ttrack('command', commandRoute, (context) => context.command);\n\t\t\t\ttrack('autocomplete', commandRoute, (context) => context.command);\n\t\t\t\ttrack('interactionHandler', handlerRoute, (context) => context.handler);\n\n\t\t\t\tconst changed = (event) => (pieces, path) => {\n\t\t\t\t\tconst list = Array.isArray(pieces) ? pieces : [pieces];\n\t\t\t\t\tconst store = list[0]?.store?.name ?? 'pieces';\n\t\t\t\t\tsend('hmr', { event, path, store, names: list.map((piece) => piece.name) });\n\t\t\t\t\tif (store === 'commands') snapshot(client);\n\t\t\t\t};\n\t\t\t\tclient.on('hmrStart', (paths) => send('hmr', { event: 'start', paths }));\n\t\t\t\tclient.on('hmrStop', () => send('hmr', { event: 'stop' }));\n\t\t\t\tclient.on('hmrPieceReloaded', changed('reloaded'));\n\t\t\t\tclient.on('hmrPiecesLoaded', changed('loaded'));\n\t\t\t\tclient.on('hmrPieceUnloaded', changed('unloaded'));\n\t\t\t\tclient.on('hmrError', (error, path) => send('hmr', { event: 'error', path, ...describe(error) }));\n\t\t\t},\n\t\t\tpostListen(client) {\n\t\t\t\tif (process.env[${JSON.stringify(DUMP_ENV)}] === '1') return;\n\t\t\t\tconst server = client.server;\n\t\t\t\tserver?.on?.('request', (request, response) => {\n\t\t\t\t\tconst at = performance.now();\n\t\t\t\t\tresponse.once('finish', () =>\n\t\t\t\t\t\tsend('request', {\n\t\t\t\t\t\t\tmethod: String(request.method),\n\t\t\t\t\t\t\tpath: String(request.url),\n\t\t\t\t\t\t\tstatus: response.statusCode,\n\t\t\t\t\t\t\tms: Math.round(performance.now() - at)\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\tfor (const store of container.stores.values()) {\n\t\t\t\t\tsend('pieces', {\n\t\t\t\t\t\tstore: store.name,\n\t\t\t\t\t\tpieces: [...store.values()].map((piece) => ({ name: piece.name, path: piece.location.full }))\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tsnapshot(client);\n\t\t\t\tconst address = server?.address?.();\n\t\t\t\tsend('ready', { clientId: client.id, port: address !== null && typeof address === 'object' ? address.port : null });\n\t\t\t}\n\t\t});\n\n\t\tprocess.on('message', (message) => {\n\t\t\tif (message === null || typeof message !== 'object' || message.source !== ${JSON.stringify(CLI_SOURCE)}) return;\n\t\t\tif (message.type !== 'commands:refresh') return;\n\t\t\tPromise.resolve()\n\t\t\t\t.then(async () => {\n\t\t\t\t\tconst client = container.client;\n\t\t\t\t\tconst global = await client.registry.pushGlobalCommands();\n\t\t\t\t\tconst guilds = await client.registry.pushGuildRestrictedCommands();\n\t\t\t\t\tconst failed = guilds.find((result) => result.status === 'rejected');\n\t\t\t\t\tif (failed) throw failed.reason;\n\t\t\t\t\t// A guild that lost its last command is no longer in the registry, so nothing above overwrote it.\n\t\t\t\t\tconst clear = Array.isArray(message.clearGuilds) ? message.clearGuilds.filter((id) => typeof id === 'string') : [];\n\t\t\t\t\tfor (const id of clear) {\n\t\t\t\t\t\tawait container.rest.put('/applications/' + client.id + '/guilds/' + id + '/commands', { body: [] });\n\t\t\t\t\t}\n\t\t\t\t\tsend('refreshed', { ok: true, global: global.length, guilds: guilds.length, cleared: clear.length });\n\t\t\t\t})\n\t\t\t\t.catch((error) => send('refreshed', { ok: false, ...describe(error) }));\n\t\t});\n\t\t// Listening for messages keeps the channel, and with it the process, alive: a bot that is done must still exit.\n\t\tprocess.channel?.unref?.();\n\t} catch {}\n}\n`;\n}\n\n/**\n * `node` arguments that preload the bridge into the bot. Nothing is preloaded when the project's framework cannot be\n * resolved or predates object plugins: `stars dev` then shows what the bot prints, as it did before the bridge.\n *\n * The framework is resolved from the project with the `import` condition, the way `moduleImportArgs` does, so the\n * plugin lands on the `Client` the bot constructs. A build that bundles the framework into its output (Vite, Nitro)\n * has its own copy, which the preload cannot reach: the bridge stays silent there too.\n */\nexport function bridgeImportArgs(config: ResolvedStarsConfig): string[] {\n\tconst version = findInstalledVersion(config.root, '@wolfstar/http-framework');\n\tif (version === null || !isAtLeast(version, BRIDGE_FRAMEWORK_VERSION)) return [];\n\n\tlet framework: string;\n\ttry {\n\t\tframework = resolveSync('@wolfstar/http-framework', { url: pathToFileURL(`${config.root}/package.json`).href });\n\t} catch {\n\t\treturn [];\n\t}\n\n\treturn ['--import', `data:text/javascript,${encodeURIComponent(bridgeModuleSource(framework))}`];\n}\n\n/** Compares the `major.minor.patch` of two versions; a prerelease counts as its release. */\nexport function isAtLeast(version: string, minimum: string): boolean {\n\tconst parse = (value: string) =>\n\t\tvalue\n\t\t\t.split('-', 1)[0]!\n\t\t\t.split('.')\n\t\t\t.map((part) => Number.parseInt(part, 10) || 0);\n\tconst [a, b] = [parse(version), parse(minimum)];\n\tfor (let index = 0; index < 3; index++) {\n\t\tconst difference = (a[index] ?? 0) - (b[index] ?? 0);\n\t\tif (difference !== 0) return difference > 0;\n\t}\n\n\treturn true;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { i as shouldUseColor } from "./output-mode-Bp7Da8vJ.js";
2
- import { t as cliDiagnostics } from "./diagnostics-CZBVxlpj.js";
3
- import { n as createBuilder, t as Locales } from "./locales-C7X8VNmK.js";
4
- import { a as projectArgs, n as loadProject, o as resolveCwd, t as applyEnvOptions } from "./hooks-H9kjLCMk.js";
5
- import { n as reportWarnings, t as prepareProject } from "./_shared-B-hRZDRS.js";
2
+ import { t as cliDiagnostics } from "./diagnostics-BysbNnPD.js";
3
+ import { n as createBuilder, t as Locales } from "./locales-BJAP4HYf.js";
4
+ import { d as resolveCwd, n as loadProject, t as applyEnvOptions, u as projectArgs } from "./hooks-Cy6F2bhH.js";
5
+ import { n as reportWarnings, t as prepareProject } from "./_shared-BIQK0Bhm.js";
6
6
  import { defineCommand } from "citty";
7
7
  import { displayPath } from "@wolfstar/schema";
8
8
  import { createColors } from "colorette";
@@ -53,4 +53,4 @@ var build_default = defineCommand({
53
53
 
54
54
  //#endregion
55
55
  export { build_default as default };
56
- //# sourceMappingURL=build-CPnSLYME.js.map
56
+ //# sourceMappingURL=build-DJcc95Yq.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-CPnSLYME.js","names":[],"sources":["../src/commands/build.ts"],"sourcesContent":["import { displayPath } from '@wolfstar/schema';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { createBuilder } from '../builders/index.js';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { applyEnvOptions, loadProject } from '../utils/hooks.js';\nimport { Locales } from '../utils/locales.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { prepareProject, reportWarnings } from './_shared.js';\n\nexport interface BuildTaskOptions extends ProjectArgs {\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport async function runBuild(options: BuildTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() });\n\tconst { config: resolved, hooks } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tawait reportWarnings(resolved, (text) => process.stderr.write(`${text}\\n`));\n\n\tif (resolved.build.tool === 'none') {\n\t\tawait prepareProject(resolved, hooks);\n\t\tstdout.write(`${colors.dim('stars')} nothing to build, ${displayPath(resolved.root, resolved.entry)} runs as-is (build.tool is 'none')\\n`);\n\t\treturn;\n\t}\n\n\t// Same hook order as `stars dev`: `env:options` and `builder:created` before `prepare:*`.\n\tconst config = await applyEnvOptions(resolved, hooks);\n\tconst builder = await createBuilder(config, hooks);\n\tawait hooks.callHook('builder:created', builder, config);\n\tawait prepareProject(config, hooks);\n\tbuilder.on('log', (level, text) => {\n\t\tconst paint = level === 'error' ? colors.red : level === 'warn' ? colors.yellow : (value: string) => value;\n\t\tstdout.write(`${paint(text)}\\n`);\n\t});\n\n\tstdout.write(`${colors.dim('stars')} building with ${colors.bold(config.build.tool)}…\\n`);\n\tawait hooks.callHook('build:before', config);\n\tconst outcome = await builder.build();\n\tawait hooks.callHook('build:done', outcome, config);\n\tif (!outcome.ok) {\n\t\tthrow cliDiagnostics.BUILD_FAILED({ message: outcome.message ?? '' });\n\t}\n\tnew Locales(config).copy();\n\n\tstdout.write(`${colors.dim('stars')} ${colors.green(`built in ${outcome.durationMs}ms`)} → ${displayPath(config.root, config.build.output)}\\n`);\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'build',\n\t\tdescription: 'Build the project once with the configured build tool'\n\t},\n\targs: { ...projectArgs },\n\tasync run({ args }) {\n\t\tawait runBuild({ config: args.config, cwd: args.cwd });\n\t}\n});\n"],"mappings":";;;;;;;;;;AAeA,eAAsB,SAAS,SAA0C;CACxE,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,EAAE,CAAC;CAC1D,MAAM,EAAE,QAAQ,UAAU,UAAU,MAAM,YAAY;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC9G,MAAM,eAAe,WAAW,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG,CAAC;CAE1E,IAAI,SAAS,MAAM,SAAS,QAAQ;EACnC,MAAM,eAAe,UAAU,KAAK;EACpC,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,qBAAqB,YAAY,SAAS,MAAM,SAAS,KAAK,EAAE,qCAAqC;EACzI;CACD;CAGA,MAAM,SAAS,MAAM,gBAAgB,UAAU,KAAK;CACpD,MAAM,UAAU,MAAM,cAAc,QAAQ,KAAK;CACjD,MAAM,MAAM,SAAS,mBAAmB,SAAS,MAAM;CACvD,MAAM,eAAe,QAAQ,KAAK;CAClC,QAAQ,GAAG,QAAQ,OAAO,SAAS;EAClC,MAAM,QAAQ,UAAU,UAAU,OAAO,MAAM,UAAU,SAAS,OAAO,UAAU,UAAkB;EACrG,OAAO,MAAM,GAAG,MAAM,IAAI,EAAE,GAAG;CAChC,CAAC;CAED,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,iBAAiB,OAAO,KAAK,OAAO,MAAM,IAAI,EAAE,IAAI;CACxF,MAAM,MAAM,SAAS,gBAAgB,MAAM;CAC3C,MAAM,UAAU,MAAM,QAAQ,MAAM;CACpC,MAAM,MAAM,SAAS,cAAc,SAAS,MAAM;CAClD,IAAI,CAAC,QAAQ,IACZ,MAAM,eAAe,aAAa,EAAE,SAAS,QAAQ,WAAW,GAAG,CAAC;CAErE,IAAI,QAAQ,MAAM,CAAC,CAAC,KAAK;CAEzB,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,MAAM,YAAY,QAAQ,WAAW,GAAG,EAAE,KAAK,YAAY,OAAO,MAAM,OAAO,MAAM,MAAM,EAAE,GAAG;AAC/I;AAEA,oBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM,EAAE,GAAG,YAAY;CACvB,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,SAAS;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;EAAI,CAAC;CACtD;AACD,CAAC"}
1
+ {"version":3,"file":"build-DJcc95Yq.js","names":[],"sources":["../src/commands/build.ts"],"sourcesContent":["import { displayPath } from '@wolfstar/schema';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { createBuilder } from '../builders/index.js';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { applyEnvOptions, loadProject } from '../utils/hooks.js';\nimport { Locales } from '../utils/locales.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { prepareProject, reportWarnings } from './_shared.js';\n\nexport interface BuildTaskOptions extends ProjectArgs {\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport async function runBuild(options: BuildTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() });\n\tconst { config: resolved, hooks } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tawait reportWarnings(resolved, (text) => process.stderr.write(`${text}\\n`));\n\n\tif (resolved.build.tool === 'none') {\n\t\tawait prepareProject(resolved, hooks);\n\t\tstdout.write(`${colors.dim('stars')} nothing to build, ${displayPath(resolved.root, resolved.entry)} runs as-is (build.tool is 'none')\\n`);\n\t\treturn;\n\t}\n\n\t// Same hook order as `stars dev`: `env:options` and `builder:created` before `prepare:*`.\n\tconst config = await applyEnvOptions(resolved, hooks);\n\tconst builder = await createBuilder(config, hooks);\n\tawait hooks.callHook('builder:created', builder, config);\n\tawait prepareProject(config, hooks);\n\tbuilder.on('log', (level, text) => {\n\t\tconst paint = level === 'error' ? colors.red : level === 'warn' ? colors.yellow : (value: string) => value;\n\t\tstdout.write(`${paint(text)}\\n`);\n\t});\n\n\tstdout.write(`${colors.dim('stars')} building with ${colors.bold(config.build.tool)}…\\n`);\n\tawait hooks.callHook('build:before', config);\n\tconst outcome = await builder.build();\n\tawait hooks.callHook('build:done', outcome, config);\n\tif (!outcome.ok) {\n\t\tthrow cliDiagnostics.BUILD_FAILED({ message: outcome.message ?? '' });\n\t}\n\tnew Locales(config).copy();\n\n\tstdout.write(`${colors.dim('stars')} ${colors.green(`built in ${outcome.durationMs}ms`)} → ${displayPath(config.root, config.build.output)}\\n`);\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'build',\n\t\tdescription: 'Build the project once with the configured build tool'\n\t},\n\targs: { ...projectArgs },\n\tasync run({ args }) {\n\t\tawait runBuild({ config: args.config, cwd: args.cwd });\n\t}\n});\n"],"mappings":";;;;;;;;;;AAeA,eAAsB,SAAS,SAA0C;CACxE,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,EAAE,CAAC;CAC1D,MAAM,EAAE,QAAQ,UAAU,UAAU,MAAM,YAAY;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC9G,MAAM,eAAe,WAAW,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG,CAAC;CAE1E,IAAI,SAAS,MAAM,SAAS,QAAQ;EACnC,MAAM,eAAe,UAAU,KAAK;EACpC,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,qBAAqB,YAAY,SAAS,MAAM,SAAS,KAAK,EAAE,qCAAqC;EACzI;CACD;CAGA,MAAM,SAAS,MAAM,gBAAgB,UAAU,KAAK;CACpD,MAAM,UAAU,MAAM,cAAc,QAAQ,KAAK;CACjD,MAAM,MAAM,SAAS,mBAAmB,SAAS,MAAM;CACvD,MAAM,eAAe,QAAQ,KAAK;CAClC,QAAQ,GAAG,QAAQ,OAAO,SAAS;EAClC,MAAM,QAAQ,UAAU,UAAU,OAAO,MAAM,UAAU,SAAS,OAAO,UAAU,UAAkB;EACrG,OAAO,MAAM,GAAG,MAAM,IAAI,EAAE,GAAG;CAChC,CAAC;CAED,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,iBAAiB,OAAO,KAAK,OAAO,MAAM,IAAI,EAAE,IAAI;CACxF,MAAM,MAAM,SAAS,gBAAgB,MAAM;CAC3C,MAAM,UAAU,MAAM,QAAQ,MAAM;CACpC,MAAM,MAAM,SAAS,cAAc,SAAS,MAAM;CAClD,IAAI,CAAC,QAAQ,IACZ,MAAM,eAAe,aAAa,EAAE,SAAS,QAAQ,WAAW,GAAG,CAAC;CAErE,IAAI,QAAQ,MAAM,CAAC,CAAC,KAAK;CAEzB,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,MAAM,YAAY,QAAQ,WAAW,GAAG,EAAE,KAAK,YAAY,OAAO,MAAM,OAAO,MAAM,MAAM,EAAE,GAAG;AAC/I;AAEA,oBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM,EAAE,GAAG,YAAY;CACvB,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,SAAS;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;EAAI,CAAC;CACtD;AACD,CAAC"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runMain } from "./run-Ddwrxe1O.js";
2
+ import { t as runMain } from "./run-DMM2YlP3.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  await runMain();
@@ -1,7 +1,7 @@
1
1
  import { i as shouldUseColor } from "./output-mode-Bp7Da8vJ.js";
2
- import { t as cliDiagnostics } from "./diagnostics-CZBVxlpj.js";
3
- import { i as resolveFromProject } from "./project-MD4dIo2X.js";
4
- import { a as projectArgs, n as loadProject, o as resolveCwd } from "./hooks-H9kjLCMk.js";
2
+ import { t as cliDiagnostics } from "./diagnostics-BysbNnPD.js";
3
+ import { i as resolveFromProject } from "./project-zlQStwsf.js";
4
+ import { d as resolveCwd, n as loadProject, u as projectArgs } from "./hooks-Cy6F2bhH.js";
5
5
  import { defineCommand } from "citty";
6
6
  import { join } from "node:path";
7
7
  import { displayPath } from "@wolfstar/schema";
@@ -121,4 +121,4 @@ var codegen_default = defineCommand({
121
121
 
122
122
  //#endregion
123
123
  export { codegen_default as default };
124
- //# sourceMappingURL=codegen-BqpLGLLs.js.map
124
+ //# sourceMappingURL=codegen-aJSyPz-J.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-BqpLGLLs.js","names":[],"sources":["../src/commands/codegen.ts"],"sourcesContent":["import { displayPath, type ResolvedStarsConfig } from '@wolfstar/schema';\nimport { loadProject } from '../utils/hooks.js';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { resolveFromProject } from '../utils/project.js';\n\nexport interface CodegenTaskOptions extends ProjectArgs {\n\tcheck?: boolean;\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport interface CodegenResult {\n\tgenerator: 'i18n';\n\toutput: string;\n\tstatus: 'written' | 'up-to-date' | 'outdated';\n}\n\nexport async function runCodegen(options: CodegenTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst { config } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tconst results: CodegenResult[] = [];\n\n\tif (config.codegen.i18n) results.push(await runI18n(config, Boolean(options.check)));\n\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify({ check: Boolean(options.check), results }, null, 2)}\\n`);\n\t} else if (results.length === 0) {\n\t\tstdout.write(`${colors.dim('stars')} nothing to generate (no code generators are configured)\\n`);\n\t} else {\n\t\tfor (const result of results) {\n\t\t\tconst paint = result.status === 'outdated' ? colors.red : colors.green;\n\t\t\tstdout.write(`${colors.dim('stars')} ${result.generator}: ${paint(result.status)} ${displayPath(config.root, result.output)}\\n`);\n\t\t}\n\t}\n\n\tif (results.some((result) => result.status === 'outdated')) {\n\t\tthrow cliDiagnostics.CODEGEN_OUTDATED({});\n\t}\n}\n\nasync function runI18n(config: ResolvedStarsConfig, check: boolean): Promise<CodegenResult> {\n\tconst { locales, output } = config.codegen.i18n!;\n\tconst cli = resolveFromProject(config.root, '@wolfstar/i18next-type-generator');\n\tif (!cli) {\n\t\tthrow cliDiagnostics.DEPENDENCY_MISSING({\n\t\t\tname: '@wolfstar/i18next-type-generator',\n\t\t\troot: config.root,\n\t\t\thint: 'Install it with `pnpm add -D @wolfstar/i18next-type-generator`, or set `codegen.i18n` to false.'\n\t\t});\n\t}\n\n\tif (!check) {\n\t\tawait generate(config.root, cli, locales, output);\n\t\treturn { generator: 'i18n', output, status: 'written' };\n\t}\n\n\tconst directory = await mkdtemp(join(tmpdir(), 'stars-codegen-'));\n\ttry {\n\t\tconst candidate = join(directory, 'i18next.d.ts');\n\t\tawait generate(config.root, cli, locales, candidate);\n\t\tconst [expected, actual] = await Promise.all([readFile(candidate, 'utf-8'), readFile(output, 'utf-8').catch(() => null)]);\n\t\treturn { generator: 'i18n', output, status: expected === actual ? 'up-to-date' : 'outdated' };\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true });\n\t}\n}\n\nfunction generate(root: string, cli: string, locales: string, output: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawn(process.execPath, [cli, locales, output], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });\n\t\tlet stderr = '';\n\t\tchild.stderr?.on('data', (chunk: Buffer) => (stderr += chunk.toString()));\n\t\tchild.once('error', reject);\n\t\tchild.once('exit', (code) => {\n\t\t\tif (code === 0) resolve();\n\t\t\telse reject(cliDiagnostics.CODEGEN_FAILED({ code, stderr: stderr.trim() }));\n\t\t});\n\t});\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'codegen',\n\t\tdescription: 'Run the configured code generators (i18next types)'\n\t},\n\targs: {\n\t\t...projectArgs,\n\t\tcheck: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Fail when the generated files are out of date instead of writing them',\n\t\t\tdefault: false\n\t\t},\n\t\tjson: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Print machine-readable JSON',\n\t\t\tdefault: false\n\t\t}\n\t},\n\tasync run({ args }) {\n\t\tawait runCodegen({ config: args.config, cwd: args.cwd, check: args.check, json: args.json });\n\t}\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"codegen-aJSyPz-J.js","names":[],"sources":["../src/commands/codegen.ts"],"sourcesContent":["import { displayPath, type ResolvedStarsConfig } from '@wolfstar/schema';\nimport { loadProject } from '../utils/hooks.js';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { resolveFromProject } from '../utils/project.js';\n\nexport interface CodegenTaskOptions extends ProjectArgs {\n\tcheck?: boolean;\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport interface CodegenResult {\n\tgenerator: 'i18n';\n\toutput: string;\n\tstatus: 'written' | 'up-to-date' | 'outdated';\n}\n\nexport async function runCodegen(options: CodegenTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst { config } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tconst results: CodegenResult[] = [];\n\n\tif (config.codegen.i18n) results.push(await runI18n(config, Boolean(options.check)));\n\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify({ check: Boolean(options.check), results }, null, 2)}\\n`);\n\t} else if (results.length === 0) {\n\t\tstdout.write(`${colors.dim('stars')} nothing to generate (no code generators are configured)\\n`);\n\t} else {\n\t\tfor (const result of results) {\n\t\t\tconst paint = result.status === 'outdated' ? colors.red : colors.green;\n\t\t\tstdout.write(`${colors.dim('stars')} ${result.generator}: ${paint(result.status)} ${displayPath(config.root, result.output)}\\n`);\n\t\t}\n\t}\n\n\tif (results.some((result) => result.status === 'outdated')) {\n\t\tthrow cliDiagnostics.CODEGEN_OUTDATED({});\n\t}\n}\n\nasync function runI18n(config: ResolvedStarsConfig, check: boolean): Promise<CodegenResult> {\n\tconst { locales, output } = config.codegen.i18n!;\n\tconst cli = resolveFromProject(config.root, '@wolfstar/i18next-type-generator');\n\tif (!cli) {\n\t\tthrow cliDiagnostics.DEPENDENCY_MISSING({\n\t\t\tname: '@wolfstar/i18next-type-generator',\n\t\t\troot: config.root,\n\t\t\thint: 'Install it with `pnpm add -D @wolfstar/i18next-type-generator`, or set `codegen.i18n` to false.'\n\t\t});\n\t}\n\n\tif (!check) {\n\t\tawait generate(config.root, cli, locales, output);\n\t\treturn { generator: 'i18n', output, status: 'written' };\n\t}\n\n\tconst directory = await mkdtemp(join(tmpdir(), 'stars-codegen-'));\n\ttry {\n\t\tconst candidate = join(directory, 'i18next.d.ts');\n\t\tawait generate(config.root, cli, locales, candidate);\n\t\tconst [expected, actual] = await Promise.all([readFile(candidate, 'utf-8'), readFile(output, 'utf-8').catch(() => null)]);\n\t\treturn { generator: 'i18n', output, status: expected === actual ? 'up-to-date' : 'outdated' };\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true });\n\t}\n}\n\nfunction generate(root: string, cli: string, locales: string, output: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawn(process.execPath, [cli, locales, output], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });\n\t\tlet stderr = '';\n\t\tchild.stderr?.on('data', (chunk: Buffer) => (stderr += chunk.toString()));\n\t\tchild.once('error', reject);\n\t\tchild.once('exit', (code) => {\n\t\t\tif (code === 0) resolve();\n\t\t\telse reject(cliDiagnostics.CODEGEN_FAILED({ code, stderr: stderr.trim() }));\n\t\t});\n\t});\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'codegen',\n\t\tdescription: 'Run the configured code generators (i18next types)'\n\t},\n\targs: {\n\t\t...projectArgs,\n\t\tcheck: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Fail when the generated files are out of date instead of writing them',\n\t\t\tdefault: false\n\t\t},\n\t\tjson: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Print machine-readable JSON',\n\t\t\tdefault: false\n\t\t}\n\t},\n\tasync run({ args }) {\n\t\tawait runCodegen({ config: args.config, cwd: args.cwd, check: args.check, json: args.json });\n\t}\n});\n"],"mappings":";;;;;;;;;;;;;AAyBA,eAAsB,WAAW,SAA4C;CAC5E,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,KAAK,CAAC,QAAQ,KAAK,CAAC;CAC3E,MAAM,EAAE,WAAW,MAAM,YAAY;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC7F,MAAM,UAA2B,CAAC;CAElC,IAAI,OAAO,QAAQ,MAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ,QAAQ,QAAQ,KAAK,CAAC,CAAC;CAEnF,IAAI,QAAQ,MACX,OAAO,MAAM,GAAG,KAAK,UAAU;EAAE,OAAO,QAAQ,QAAQ,KAAK;EAAG;CAAQ,GAAG,MAAM,CAAC,EAAE,GAAG;MACjF,IAAI,QAAQ,WAAW,GAC7B,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,2DAA2D;MAE/F,KAAK,MAAM,UAAU,SAAS;EAC7B,MAAM,QAAQ,OAAO,WAAW,aAAa,OAAO,MAAM,OAAO;EACjE,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,UAAU,IAAI,MAAM,OAAO,MAAM,EAAE,GAAG,YAAY,OAAO,MAAM,OAAO,MAAM,EAAE,GAAG;CAChI;CAGD,IAAI,QAAQ,MAAM,WAAW,OAAO,WAAW,UAAU,GACxD,MAAM,eAAe,iBAAiB,CAAC,CAAC;AAE1C;AAEA,eAAe,QAAQ,QAA6B,OAAwC;CAC3F,MAAM,EAAE,SAAS,WAAW,OAAO,QAAQ;CAC3C,MAAM,MAAM,mBAAmB,OAAO,MAAM,kCAAkC;CAC9E,IAAI,CAAC,KACJ,MAAM,eAAe,mBAAmB;EACvC,MAAM;EACN,MAAM,OAAO;EACb,MAAM;CACP,CAAC;CAGF,IAAI,CAAC,OAAO;EACX,MAAM,SAAS,OAAO,MAAM,KAAK,SAAS,MAAM;EAChD,OAAO;GAAE,WAAW;GAAQ;GAAQ,QAAQ;EAAU;CACvD;CAEA,MAAM,YAAY,MAAM,QAAQ,KAAK,OAAO,GAAG,gBAAgB,CAAC;CAChE,IAAI;EACH,MAAM,YAAY,KAAK,WAAW,cAAc;EAChD,MAAM,SAAS,OAAO,MAAM,KAAK,SAAS,SAAS;EACnD,MAAM,CAAC,UAAU,UAAU,MAAM,QAAQ,IAAI,CAAC,SAAS,WAAW,OAAO,GAAG,SAAS,QAAQ,OAAO,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;EACxH,OAAO;GAAE,WAAW;GAAQ;GAAQ,QAAQ,aAAa,SAAS,eAAe;EAAW;CAC7F,UAAU;EACT,MAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACrD;AACD;AAEA,SAAS,SAAS,MAAc,KAAa,SAAiB,QAA+B;CAC5F,OAAO,IAAI,SAAS,SAAS,WAAW;EACvC,MAAM,QAAQ,MAAM,QAAQ,UAAU;GAAC;GAAK;GAAS;EAAM,GAAG;GAAE,KAAK;GAAM,OAAO;IAAC;IAAU;IAAU;GAAM;GAAG,aAAa;EAAK,CAAC;EACnI,IAAI,SAAS;EACb,MAAM,QAAQ,GAAG,SAAS,UAAmB,UAAU,MAAM,SAAS,CAAE;EACxE,MAAM,KAAK,SAAS,MAAM;EAC1B,MAAM,KAAK,SAAS,SAAS;GAC5B,IAAI,SAAS,GAAG,QAAQ;QACnB,OAAO,eAAe,eAAe;IAAE;IAAM,QAAQ,OAAO,KAAK;GAAE,CAAC,CAAC;EAC3E,CAAC;CACF,CAAC;AACF;AAEA,sBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM;EACL,GAAG;EACH,OAAO;GACN,MAAM;GACN,aAAa;GACb,SAAS;EACV;EACA,MAAM;GACL,MAAM;GACN,aAAa;GACb,SAAS;EACV;CACD;CACA,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,WAAW;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;GAAK,OAAO,KAAK;GAAO,MAAM,KAAK;EAAK,CAAC;CAC5F;AACD,CAAC"}