@wolfstar/cli 2.3.1 → 2.6.0-next-20261007120201

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 (74) hide show
  1. package/README.md +83 -13
  2. package/dist/{_shared-BIQK0Bhm.js → _shared-CyzTojMA.js} +95 -20
  3. package/dist/_shared-CyzTojMA.js.map +1 -0
  4. package/dist/{bridge-BegCt_n2.js → bridge-Cbm0jX9h.js} +3 -236
  5. package/dist/bridge-Cbm0jX9h.js.map +1 -0
  6. package/dist/{build-DJcc95Yq.js → build-BbIEbOu3.js} +5 -5
  7. package/dist/{build-DJcc95Yq.js.map → build-BbIEbOu3.js.map} +1 -1
  8. package/dist/cli.js +1 -1
  9. package/dist/codegen-C2PY7RXA.js +262 -0
  10. package/dist/codegen-C2PY7RXA.js.map +1 -0
  11. package/dist/commands-3Tgwy6cj.js +16 -0
  12. package/dist/commands-3Tgwy6cj.js.map +1 -0
  13. package/dist/commands-3qXbxhXx.js +3 -0
  14. package/dist/{commands-Bh0K9iWX.js → commands-C-i3qYOz.js} +5 -93
  15. package/dist/commands-C-i3qYOz.js.map +1 -0
  16. package/dist/{completions-DMPlZ0S1.js → completions-Cs6YbuXO.js} +3 -3
  17. package/dist/{completions-DMPlZ0S1.js.map → completions-Cs6YbuXO.js.map} +1 -1
  18. package/dist/{dev-Owu3e3AO.js → dev-9YvzhtkU.js} +22 -215
  19. package/dist/dev-9YvzhtkU.js.map +1 -0
  20. package/dist/{diagnostics-BysbNnPD.js → diagnostics-Dn3CXa8J.js} +11 -2
  21. package/dist/diagnostics-Dn3CXa8J.js.map +1 -0
  22. package/dist/{doctor-DEtAAknK.js → doctor-CMGfbXS5.js} +15 -7
  23. package/dist/doctor-CMGfbXS5.js.map +1 -0
  24. package/dist/{hooks-Cy6F2bhH.js → hooks-D_RVS0ba.js} +13 -5
  25. package/dist/hooks-D_RVS0ba.js.map +1 -0
  26. package/dist/index.d.ts +21 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -2
  29. package/dist/{info-CKJVMWIT.js → info-eXsx1AfE.js} +5 -4
  30. package/dist/info-eXsx1AfE.js.map +1 -0
  31. package/dist/local-commands-CGBjc8KJ.js +95 -0
  32. package/dist/local-commands-CGBjc8KJ.js.map +1 -0
  33. package/dist/{locales-BJAP4HYf.js → locales-BzyEMQnX.js} +5 -5
  34. package/dist/{locales-BJAP4HYf.js.map → locales-BzyEMQnX.js.map} +1 -1
  35. package/dist/{nitro-B8u1iVgf.js → nitro-DufOgx8s.js} +2 -2
  36. package/dist/{nitro-B8u1iVgf.js.map → nitro-DufOgx8s.js.map} +1 -1
  37. package/dist/{prepare-BM93i3Rm.js → prepare-DKH0UIYA.js} +12 -8
  38. package/dist/prepare-DKH0UIYA.js.map +1 -0
  39. package/dist/{project-zlQStwsf.js → project-DBqQMFCk.js} +14 -3
  40. package/dist/project-DBqQMFCk.js.map +1 -0
  41. package/dist/{run-CHqwvZXf.js → run-DlNY1QM1.js} +2 -2
  42. package/dist/{run-CHqwvZXf.js.map → run-DlNY1QM1.js.map} +1 -1
  43. package/dist/tsc-9yHyx0L6.js +3 -0
  44. package/dist/{tsc-puVAcwmo.js → tsc-BArammxe.js} +3 -3
  45. package/dist/{tsc-puVAcwmo.js.map → tsc-BArammxe.js.map} +1 -1
  46. package/dist/{tsdown-Dwt3JhPw.js → tsdown-2i5h88vT.js} +2 -2
  47. package/dist/{tsdown-Dwt3JhPw.js.map → tsdown-2i5h88vT.js.map} +1 -1
  48. package/dist/{tui-nldGSD4c.js → tui-CB_qk2Z1.js} +2 -2
  49. package/dist/{tui-nldGSD4c.js.map → tui-CB_qk2Z1.js.map} +1 -1
  50. package/dist/tunnel-BNV5cuJz.js +236 -0
  51. package/dist/tunnel-BNV5cuJz.js.map +1 -0
  52. package/dist/typecheck-p_2rj9lL.js +66 -0
  53. package/dist/typecheck-p_2rj9lL.js.map +1 -0
  54. package/dist/typechecker-C3UOI2LF.js +213 -0
  55. package/dist/typechecker-C3UOI2LF.js.map +1 -0
  56. package/dist/{vite-YZUtv2Hk.js → vite-C5CN3xUu.js} +2 -2
  57. package/dist/{vite-YZUtv2Hk.js.map → vite-C5CN3xUu.js.map} +1 -1
  58. package/package.json +9 -9
  59. package/dist/_shared-BIQK0Bhm.js.map +0 -1
  60. package/dist/bridge-BegCt_n2.js.map +0 -1
  61. package/dist/codegen-aJSyPz-J.js +0 -124
  62. package/dist/codegen-aJSyPz-J.js.map +0 -1
  63. package/dist/commands-Bh0K9iWX.js.map +0 -1
  64. package/dist/commands-BlDTDvFO.js +0 -3
  65. package/dist/commands-CSB-pjgs.js +0 -15
  66. package/dist/commands-CSB-pjgs.js.map +0 -1
  67. package/dist/dev-Owu3e3AO.js.map +0 -1
  68. package/dist/diagnostics-BysbNnPD.js.map +0 -1
  69. package/dist/doctor-DEtAAknK.js.map +0 -1
  70. package/dist/hooks-Cy6F2bhH.js.map +0 -1
  71. package/dist/info-CKJVMWIT.js.map +0 -1
  72. package/dist/prepare-BM93i3Rm.js.map +0 -1
  73. package/dist/project-zlQStwsf.js.map +0 -1
  74. package/dist/tsc-C5l5TQGC.js +0 -3
package/README.md CHANGED
@@ -24,8 +24,9 @@ package has no install-time dependency on `@wolfstar/http-framework` in return,
24
24
  - `stars dev` builds the project, starts the bot, restarts it (or leaves the change to the bot's hot reload) and shows what is happening in a full-screen dashboard: status, log channels and levels you can filter, and a prompt before changed commands are redeployed (or plain logs).
25
25
  - `stars build` runs the configured build tool once.
26
26
  - `stars info` prints the resolved configuration and environment (`--json` for scripts).
27
- - `stars codegen` runs the configured code generators (`--check` for CI).
28
- - `stars prepare` generates `.stars/tsconfig.json` and the auto imports declaration file (`--check` for CI).
27
+ - `stars codegen` runs the configured code generators: i18next types and the typed options of your commands (`--check` for CI).
28
+ - `stars prepare` generates the TypeScript configuration in `.stars/` (`tsconfig.app.json` and `tsconfig.node.json`) and the auto imports declaration file (`--check` for CI).
29
+ - `stars typecheck` regenerates `.stars/` and type-checks every project once, the way `nuxt typecheck` does.
29
30
  - `stars commands` inspects, compares, deploys and cleans the application commands Discord has deployed.
30
31
  - `stars doctor` checks that the project is ready: runtime, framework, credentials, interactions endpoint, generated files.
31
32
  - `stars completions` prints the shell completion script for bash, zsh or fish.
@@ -66,14 +67,17 @@ const config = await loadStarsConfig({ cwd: process.cwd() });
66
67
  console.log(config.entry, config.build.output);
67
68
  ```
68
69
 
70
+ `loadStarsConfig` only validates what the schema owns. A top-level key that is not built in is kept as `config.moduleOptions` (it may be the options of a module, `meta.configKey`), not rejected, so a misspelled key is reported by the `stars` commands once the modules are set up, not by `loadStarsConfig`. A tool that loads the configuration on its own and wants that check calls `assertModuleOptionsClaimed(config, claimedKeys)` from `@wolfstar/schema` (also exported by `@wolfstar/http-framework/config`) with the `meta.configKey`s of the modules it installed, or `[]` when it installs none.
71
+
69
72
  ## Commands
70
73
 
71
74
  ```sh
72
- stars dev [--no-tui] [--layout <auto|dashboard|panel>] [--channel <name>] [--level <level>] [--theme <name>] [--config <file>] [--cwd <dir>]
75
+ stars dev [--no-tui] [--tunnel|--no-tunnel] [--layout <auto|dashboard|panel>] [--channel <name>] [--level <level>] [--theme <name>] [--config <file>] [--cwd <dir>]
73
76
  stars build [--config <file>] [--cwd <dir>]
74
77
  stars info [--json] [--config <file>] [--cwd <dir>]
75
78
  stars codegen [--check] [--json] [--config <file>] [--cwd <dir>]
76
79
  stars prepare [--check] [--json] [--config <file>] [--cwd <dir>]
80
+ stars typecheck [--config <file>] [--cwd <dir>]
77
81
  stars commands [list|clean|diff|deploy] [--guild <id>] [--name <name>] [--check] [--yes] [--json]
78
82
  stars doctor [--online] [--json] [--config <file>] [--cwd <dir>]
79
83
  stars completions <bash|zsh|fish>
@@ -275,6 +279,48 @@ fills in on its own (`id`, `version`, defaults such as `nsfw: false`) are ignore
275
279
  overwrite: a deployed command the project no longer defines is deleted, which is why it asks first and refuses to
276
280
  run without `--yes` outside a terminal, or with `--json`.
277
281
 
282
+ ### `stars codegen`
283
+
284
+ Runs the code generators `codegen` in `stars.config` enables, writing their files (`--check` fails with
285
+ `CODEGEN_OUTDATED` instead when one is stale, `--json` prints `{ check, results }`):
286
+
287
+ - `codegen.i18n` types the i18next resources with `@wolfstar/i18next-type-generator`. It is on by default when
288
+ `src/locales/en-US` exists.
289
+ - `codegen.commands` types the options of every command from its builder. It is off by default: `true` writes
290
+ `src/@types/commands.d.ts`, `{ output }` picks another file.
291
+
292
+ ```typescript
293
+ export default defineConfig({ codegen: { commands: true } });
294
+ ```
295
+
296
+ The generated file augments `CommandOptionsRegistry` with one entry per command path (`'ping'`, `'math add'`,
297
+ `'subscriptions twitch add'`), and `Command.OptionsOf<'math add'>` reads it, so the handler needs no hand-written
298
+ `interface Options` that can drift from the builder:
299
+
300
+ ```typescript
301
+ public add(interaction: Command.ChatInputInteraction, { left, right }: Command.OptionsOf<'math add'>) {}
302
+ ```
303
+
304
+ Every option has the shape the framework resolves it to at runtime (`user` is `TransformedArguments.User`, `role` is
305
+ `APIRole`, ...), a `required` option is not optional, `choices` are a literal union and `channel_types` narrow a
306
+ channel. A localized command is keyed by its default name, the one the handler receives. Context menu commands have no
307
+ options and are skipped. For an autocomplete, `Command.AutocompleteArguments<Command.OptionsOf<'math add'>>` types
308
+ `focused` from the same entry.
309
+
310
+ The types come from what the bot registers, so loops and factories need no special care: `stars codegen` reads the
311
+ commands from the built bot the way `stars commands diff` does (run `stars build` first), by starting it in a mode
312
+ that loads its pieces, reports its registry and exits before it listens or talks to Discord. It does not call
313
+ `client.login`, but the bot still has to get through its own setup: values it reads while starting (`DISCORD_CLIENT_ID`,
314
+ `DISCORD_PUBLIC_KEY`, ...) have to be set, with placeholders in CI where the real secrets are not available:
315
+
316
+ ```sh
317
+ DISCORD_CLIENT_ID=1 DISCORD_TOKEN=x DISCORD_PUBLIC_KEY=0 stars codegen --check
318
+ ```
319
+
320
+ It is not part of `stars dev` or `stars prepare` (which has no build to read from yet): run `stars codegen` after
321
+ changing an option, and `stars codegen --check` in CI to keep the file up to date. A path registered more than once
322
+ (a command restricted to several guilds) keeps its first definition.
323
+
278
324
  ### `stars doctor`
279
325
 
280
326
  Checks what a project needs before `stars dev` can do its job, and says what to do about each problem:
@@ -291,7 +337,7 @@ stars doctor v2.3.0
291
337
  → Stop it, or set another HTTP_PORT, unless it is this bot running.
292
338
  ℹ tunnel No tunnel: Discord cannot reach a bot on localhost
293
339
  → Set `dev.tunnel: true` for a cloudflared quick tunnel, or press t in `stars dev`.
294
- ⚠ prepare Out of date: .stars/tsconfig.json
340
+ ⚠ prepare Out of date: .stars/tsconfig.app.json, .stars/tsconfig.node.json
295
341
  → Run `stars prepare`.
296
342
 
297
343
  1 error(s), 2 warning(s)
@@ -325,7 +371,8 @@ Three `dev` options round out the dev loop (all documented in
325
371
  mode), `tsz` (the tsc-compatible checker, re-run after every build since it has no watch mode), or `auto` — the
326
372
  default, which uses `golar` when the project depends on it and `tsc` otherwise.
327
373
  - Pressing `t` opens and closes a `cloudflared` quick tunnel without configuration. `dev.tunnel: true` opens it at startup so Discord can reach the bot's interactions endpoint from the
328
- internet; a string is an https URL you already serve, which the CLI only probes. `dev.tunnel.updateEndpoint` writes
374
+ internet, and so does `stars dev --tunnel` for one session, whatever `dev.tunnel` says (`--no-tunnel` keeps it closed at
375
+ start; `t` still opens it). With `--no-tui` the URL is printed on the `tunnel` channel; a string is an https URL you already serve, which the CLI only probes. `dev.tunnel.updateEndpoint` writes
329
376
  the URL to the Discord application, and is opt-in because it edits a live application.
330
377
  - `dev.logFile` (default `.stars/dev.log`) mirrors the session's logs to disk, so a run can be read back after the
331
378
  terminal UI is gone. Set it to `false` to disable it. It is truncated on every run; `dev.logs.dir` (for example
@@ -368,7 +415,8 @@ and which options the block sets.
368
415
  | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
369
416
  | `3` (EOL) | Legacy behaviour: a `tsdown.config.*` drives the build, auto imports off unless asked for; prints `COMPATIBILITY_VERSION_EOL` |
370
417
  | `4` | `tsdown` configured from `stars.config` alone, auto imports on and wired in, `'auto'` picks `tsdown` for TypeScript |
371
- | `5` (default) | Everything in `4`, plus `env` registered automatically when the project depends on `@wolfstar/env-utilities` |
418
+ | `5` | Everything in `4`, plus `env` registered automatically when the project depends on `@wolfstar/env-utilities` |
419
+ | `6` (default) | Everything in `5`, plus the generated tsconfig split into `.stars/tsconfig.app.json` and `.stars/tsconfig.node.json` |
372
420
 
373
421
  ### Environment and hooks
374
422
 
@@ -435,7 +483,30 @@ package.json imports, and ESNext/DOM libraries. Use `import type` and `export ty
435
483
  These options apply to tsdown and Vite; tsc keeps its emit-compatible settings. Sapphire's decorator options and
436
484
  the ES2022 target remain in effect. This does not enable Stars' experimental Nitro runtime integration.
437
485
 
438
- Run `stars prepare` and extend the generated config from your project's `tsconfig.json`:
486
+ From compatibility version 6, `stars prepare` writes two files, the way a Nuxt 4 project has them:
487
+
488
+ - `.stars/tsconfig.app.json` is the bot: everything under the entry's directory plus the auto imports declaration,
489
+ with the `~`/`@`/`~~`/`@@` paths and the options above. `tsdown` builds with it.
490
+ - `.stars/tsconfig.node.json` is what Node runs without a bundler: `stars.config.*`, `vitest.config.*`,
491
+ `tsdown.config.*`, `vite.config.*` and `scripts/**`. It uses `NodeNext` resolution, `types: ["node"]`, no DOM
492
+ library, no aliases or auto imports and no decorators, and leaves the entry's directory to the app project.
493
+
494
+ Your own `tsconfig.json` only references them:
495
+
496
+ ```json
497
+ {
498
+ "files": [],
499
+ "references": [{ "path": "./.stars/tsconfig.app.json" }, { "path": "./.stars/tsconfig.node.json" }]
500
+ }
501
+ ```
502
+
503
+ `stars typecheck` regenerates `.stars/` and runs your checker (`dev.typecheck.checker`: `golar` or `tsc`; bare `tsc -p`
504
+ on that root would check nothing) once per project. `stars dev`'s `dev.typecheck` watches the app project. A root
505
+ `tsconfig.json` that still extends `./.stars/tsconfig.json` gets a `TSCONFIG_LEGACY_EXTENDS` warning from
506
+ `stars prepare` and `stars doctor`: update it once, or keep `future.compatibilityVersion` at `5`.
507
+
508
+ On `5` and `4` nothing changes: `stars prepare` writes the single `.stars/tsconfig.json`, which you extend from your
509
+ project's `tsconfig.json`:
439
510
 
440
511
  ```json
441
512
  {
@@ -443,18 +514,17 @@ Run `stars prepare` and extend the generated config from your project's `tsconfi
443
514
  }
444
515
  ```
445
516
 
446
- New tsdown projects already extend this file and run `stars prepare` through `postinstall`.
447
- Keep your existing compiler options alongside `extends`. `stars dev` and `stars build` also regenerate this file.
448
- For tsdown builds, `@/` and `~/` resolve to the entry file's directory (normally `src/`), while `@@/` and `~~/`
517
+ Keep your existing compiler options alongside `extends`. `stars dev` and `stars build` also regenerate the generated
518
+ files. For tsdown builds, `@/` and `~/` resolve to the entry file's directory (normally `src/`), while `@@/` and `~~/`
449
519
  resolve to the project root. Filesystem aliases in `stars.config.ts`'s `tsdown.alias` are included too, with custom
450
520
  values taking precedence. Legacy builds using a separate tsdown config only include aliases declared in
451
521
  `stars.config.ts`. Other build tools do not get tsdown aliases, since TypeScript alone does not rewrite imports.
452
522
 
453
- The generated config includes source files and the auto imports declaration, including a custom `imports.dts`
523
+ The generated app config includes source files and the auto imports declaration, including a custom `imports.dts`
454
524
  location. Explicit `include` or `compilerOptions.paths` in your own tsconfig replace the inherited values;
455
525
  remove manually duplicated paths to use the generated aliases. Generation works with `imports: false` too.
456
- Use `stars prepare --check` to check both generated files without writing them. Do not edit `.stars/tsconfig.json`
457
- by hand; keep `.stars/` ignored by Git and run `stars prepare` after installing dependencies on a fresh checkout.
526
+ Use `stars prepare --check` to check the generated files without writing them. Do not edit anything in `.stars/` by
527
+ hand; keep `.stars/` ignored by Git and run `stars prepare` after installing dependencies on a fresh checkout.
458
528
 
459
529
  ## Server integrations
460
530
 
@@ -1,20 +1,69 @@
1
1
  import { r as formatError } from "./errors-COFWKKKw.js";
2
+ import { t as cliDiagnostics } from "./diagnostics-Dn3CXa8J.js";
2
3
  import { t as loadAutoImportsModule } from "./framework-auto-imports-Gu_PXSyr.js";
3
- import { a as modulesPreloadWarning, o as prepareModulesPreload } from "./hooks-Cy6F2bhH.js";
4
+ import { a as modulesPreloadWarning, o as prepareModulesPreload } from "./hooks-D_RVS0ba.js";
4
5
  import { createRequire } from "node:module";
5
6
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
7
+ import { SPLIT_TSCONFIG_VERSION } from "@wolfstar/schema";
6
8
  import { mkdir, readFile, writeFile } from "node:fs/promises";
7
9
 
8
10
  //#region src/utils/tsconfig.ts
9
11
  const require = createRequire(import.meta.url);
10
- const sapphireOptions = Object.assign({}, ...[
12
+ const presetOptions = (ids) => Object.assign({}, ...ids.map((id) => require(id).compilerOptions));
13
+ const strictOptions = presetOptions(["@sapphire/ts-config", "@sapphire/ts-config/extra-strict"]);
14
+ const sapphireOptions = presetOptions([
11
15
  "@sapphire/ts-config",
12
16
  "@sapphire/ts-config/extra-strict",
13
17
  "@sapphire/ts-config/decorators"
14
- ].map((id) => require(id).compilerOptions));
15
- /** Generates an extendable config without changing the project's own tsconfig. */
18
+ ]);
19
+ /** The root config files that run in Node and never go through the bundler. */
20
+ const NODE_PROJECT_FILES = [
21
+ "stars.config.*",
22
+ "vitest.config.*",
23
+ "tsdown.config.*",
24
+ "vite.config.*",
25
+ "scripts/**/*"
26
+ ];
27
+ /**
28
+ * Generates the TypeScript configuration without changing the project's own tsconfig: one extendable
29
+ * `.stars/tsconfig.json` below compatibility version 6, and from 6 the `.stars/tsconfig.app.json` (the bot sources) and
30
+ * `.stars/tsconfig.node.json` (the root files that run in Node) a solution-style `tsconfig.json` references.
31
+ * Returns one status per file.
32
+ */
16
33
  async function prepareTsconfig(config, check = false) {
17
- const path = join(config.root, ".stars", "tsconfig.json");
34
+ if (config.future.compatibilityVersion < SPLIT_TSCONFIG_VERSION) return [await emit(config, "tsconfig", appTsconfig(config, "tsconfig"), check)];
35
+ return [await emit(config, "app", appTsconfig(config, "app"), check), await emit(config, "node", nodeTsconfig(config), check)];
36
+ }
37
+ /**
38
+ * Reports a root `tsconfig.json` that still extends the single `.stars/tsconfig.json` of compatibility versions below 6,
39
+ * a file `stars prepare` no longer writes from version 6 on. A project moving to 6 updates it once.
40
+ */
41
+ async function findLegacyRootTsconfig(config) {
42
+ if (config.future.compatibilityVersion < SPLIT_TSCONFIG_VERSION) return null;
43
+ const path = join(config.root, "tsconfig.json");
44
+ const content = await readFile(path, "utf-8").catch(() => null);
45
+ if (content === null || !/["']extends["']\s*:\s*(?:\[[^\]]*)?["']\.\/\.stars\/tsconfig\.json["']/.test(content)) return null;
46
+ return cliDiagnostics.TSCONFIG_LEGACY_EXTENDS({ file: relative(config.root, path) || "tsconfig.json" });
47
+ }
48
+ async function emit(config, name, compilerConfig, check) {
49
+ const path = join(config.root, ".stars", name === "tsconfig" ? "tsconfig.json" : `tsconfig.${name}.json`);
50
+ const content = `${JSON.stringify(compilerConfig, null, 2)}\n`;
51
+ if (check) return {
52
+ name,
53
+ path,
54
+ status: await readFile(path, "utf-8").catch(() => null) === content ? "up-to-date" : "outdated"
55
+ };
56
+ await mkdir(dirname(path), { recursive: true });
57
+ await writeFile(path, content);
58
+ return {
59
+ name,
60
+ path,
61
+ status: "written"
62
+ };
63
+ }
64
+ /** What `.stars/tsconfig.json` has always been: the bot sources, with the aliases and the bundler's compiler options. */
65
+ function appTsconfig(config, name) {
66
+ const path = join(config.root, ".stars", name === "tsconfig" ? "tsconfig.json" : `tsconfig.${name}.json`);
18
67
  const fromGenerated = (target) => `./${relative(dirname(path), target).replaceAll("\\", "/")}`;
19
68
  const paths = {};
20
69
  if (config.build.tool === "tsdown") {
@@ -48,7 +97,7 @@ async function prepareTsconfig(config, check = false) {
48
97
  paths[`${alias}/*`] = [`${resolved}/*`];
49
98
  }
50
99
  }
51
- const content = `${JSON.stringify({
100
+ return {
52
101
  compilerOptions: {
53
102
  ...sapphireOptions,
54
103
  ...config.build.tool === "tsdown" || config.build.tool === "vite" ? {
@@ -66,21 +115,44 @@ async function prepareTsconfig(config, check = false) {
66
115
  target: "ES2022",
67
116
  forceConsistentCasingInFileNames: true,
68
117
  skipLibCheck: true,
69
- tsBuildInfoFile: "./tsconfig.tsbuildinfo",
118
+ tsBuildInfoFile: name === "tsconfig" ? "./tsconfig.tsbuildinfo" : `./tsconfig.${name}.tsbuildinfo`,
70
119
  paths
71
120
  },
72
121
  include: [`${fromGenerated(dirname(config.entry))}/**/*`, ...config.imports.enabled ? [fromGenerated(config.imports.dts)] : []],
73
122
  exclude: [fromGenerated(join(config.root, "node_modules")), fromGenerated(config.build.outDir)]
74
- }, null, 2)}\n`;
75
- if (check) return {
76
- path,
77
- status: await readFile(path, "utf-8").catch(() => null) === content ? "up-to-date" : "outdated"
78
123
  };
79
- await mkdir(dirname(path), { recursive: true });
80
- await writeFile(path, content);
124
+ }
125
+ /**
126
+ * The root files that run in Node: `stars.config.*`, `vitest.config.*`, `scripts/**` and the like. No DOM, no
127
+ * bundler resolution, no aliases or auto imports (none of it applies to a file Node loads itself), and no decorators.
128
+ */
129
+ function nodeTsconfig(config) {
130
+ const path = join(config.root, ".stars", "tsconfig.node.json");
131
+ const fromGenerated = (target) => `./${relative(dirname(path), target).replaceAll("\\", "/")}`;
132
+ const source = dirname(config.entry);
133
+ const include = NODE_PROJECT_FILES.map((pattern) => fromGenerated(join(config.root, pattern)));
134
+ if (config.configFile !== null) include.push(fromGenerated(config.configFile));
81
135
  return {
82
- path,
83
- status: "written"
136
+ compilerOptions: {
137
+ ...strictOptions,
138
+ target: "ES2022",
139
+ module: "NodeNext",
140
+ moduleResolution: "NodeNext",
141
+ moduleDetection: "force",
142
+ lib: ["ESNext"],
143
+ types: ["node"],
144
+ allowJs: true,
145
+ noEmit: true,
146
+ forceConsistentCasingInFileNames: true,
147
+ skipLibCheck: true,
148
+ tsBuildInfoFile: "./tsconfig.node.tsbuildinfo"
149
+ },
150
+ include: [...new Set(include)],
151
+ exclude: [
152
+ fromGenerated(join(config.root, "node_modules")),
153
+ fromGenerated(config.build.outDir),
154
+ ...source === config.root ? [] : [fromGenerated(source)]
155
+ ]
84
156
  };
85
157
  }
86
158
 
@@ -120,10 +192,11 @@ async function prepareAutoImports(config, check = false) {
120
192
  /** Prepares TypeScript configuration and auto import declarations before building. */
121
193
  async function prepareProject(config, hooks, check = false) {
122
194
  await hooks?.callHook("prepare:before", config);
123
- const tsconfig = await prepareTsconfig(config, check);
195
+ const tsconfigs = await prepareTsconfig(config, check);
124
196
  const result = {
125
197
  ...await prepareAutoImports(config, check),
126
- tsconfig,
198
+ tsconfig: tsconfigs[0],
199
+ tsconfigs,
127
200
  modules: await prepareModulesPreload(config, check)
128
201
  };
129
202
  await hooks?.callHook("prepare:done", config, {
@@ -133,15 +206,17 @@ async function prepareProject(config, hooks, check = false) {
133
206
  return result;
134
207
  }
135
208
  /**
136
- * Prints the non-fatal configuration diagnostics (e.g. an end-of-life compatibility version), and, unless `production`
209
+ * Prints the non-fatal configuration diagnostics (e.g. an end-of-life compatibility version, or a root `tsconfig.json` still extending `.stars/tsconfig.json`), and, unless `production`
137
210
  * is `false` (`stars dev`, which does that itself), what a production start of the project still needs to do.
138
211
  */
139
212
  async function reportWarnings(config, write, options = {}) {
140
213
  for (const warning of config.warnings) write(await formatError(warning));
214
+ const legacyTsconfig = await findLegacyRootTsconfig(config);
215
+ if (legacyTsconfig) write(await formatError(legacyTsconfig));
141
216
  const preload = options.production === false ? null : modulesPreloadWarning(config);
142
217
  if (preload) write(await formatError(preload));
143
218
  }
144
219
 
145
220
  //#endregion
146
- export { reportWarnings as n, prepareProject as t };
147
- //# sourceMappingURL=_shared-BIQK0Bhm.js.map
221
+ export { reportWarnings as n, findLegacyRootTsconfig as r, prepareProject as t };
222
+ //# sourceMappingURL=_shared-CyzTojMA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_shared-CyzTojMA.js","names":[],"sources":["../src/utils/tsconfig.ts","../src/commands/_shared.ts"],"sourcesContent":["import { SPLIT_TSCONFIG_VERSION, type ResolvedStarsConfig } from '@wolfstar/schema';\nimport { createRequire } from 'node:module';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport type { Diagnostic } from 'nostics';\nimport { cliDiagnostics } from './diagnostics.js';\n\nconst require = createRequire(import.meta.url);\nconst presetOptions = (ids: string[]) =>\n\tObject.assign({}, ...ids.map((id) => (require(id) as { compilerOptions: Record<string, unknown> }).compilerOptions)) as Record<string, unknown>;\n// Strictness belongs to every project; the decorator options only to the code the bundler compiles.\nconst strictOptions = presetOptions(['@sapphire/ts-config', '@sapphire/ts-config/extra-strict']);\nconst sapphireOptions = presetOptions(['@sapphire/ts-config', '@sapphire/ts-config/extra-strict', '@sapphire/ts-config/decorators']);\n\nexport type TsconfigName = 'tsconfig' | 'app' | 'node';\n\nexport interface TsconfigStatus {\n\t/** `tsconfig` is the single file of compatibility versions below 6, `app` and `node` the split ones. */\n\tname: TsconfigName;\n\tpath: string;\n\tstatus: 'written' | 'up-to-date' | 'outdated';\n}\n\n/** The root config files that run in Node and never go through the bundler. */\nconst NODE_PROJECT_FILES = ['stars.config.*', 'vitest.config.*', 'tsdown.config.*', 'vite.config.*', 'scripts/**/*'] as const;\n\n/**\n * Generates the TypeScript configuration without changing the project's own tsconfig: one extendable\n * `.stars/tsconfig.json` below compatibility version 6, and from 6 the `.stars/tsconfig.app.json` (the bot sources) and\n * `.stars/tsconfig.node.json` (the root files that run in Node) a solution-style `tsconfig.json` references.\n * Returns one status per file.\n */\nexport async function prepareTsconfig(config: ResolvedStarsConfig, check = false): Promise<TsconfigStatus[]> {\n\tif (config.future.compatibilityVersion < SPLIT_TSCONFIG_VERSION) {\n\t\treturn [await emit(config, 'tsconfig', appTsconfig(config, 'tsconfig'), check)];\n\t}\n\n\t// Written for every build tool: the node project is about files Node runs whatever builds the bot. A `tsc` or `none`\n\t// project keeps its own tsconfig for the bot sources, so for it the app file is generated but nothing references it.\n\treturn [await emit(config, 'app', appTsconfig(config, 'app'), check), await emit(config, 'node', nodeTsconfig(config), check)];\n}\n\n/**\n * Reports a root `tsconfig.json` that still extends the single `.stars/tsconfig.json` of compatibility versions below 6,\n * a file `stars prepare` no longer writes from version 6 on. A project moving to 6 updates it once.\n */\nexport async function findLegacyRootTsconfig(config: ResolvedStarsConfig): Promise<Diagnostic | null> {\n\tif (config.future.compatibilityVersion < SPLIT_TSCONFIG_VERSION) return null;\n\n\tconst path = join(config.root, 'tsconfig.json');\n\tconst content = await readFile(path, 'utf-8').catch(() => null);\n\tif (content === null || !/[\"']extends[\"']\\s*:\\s*(?:\\[[^\\]]*)?[\"']\\.\\/\\.stars\\/tsconfig\\.json[\"']/.test(content)) return null;\n\n\treturn cliDiagnostics.TSCONFIG_LEGACY_EXTENDS({ file: relative(config.root, path) || 'tsconfig.json' });\n}\n\nasync function emit(config: ResolvedStarsConfig, name: TsconfigName, compilerConfig: object, check: boolean): Promise<TsconfigStatus> {\n\tconst path = join(config.root, '.stars', name === 'tsconfig' ? 'tsconfig.json' : `tsconfig.${name}.json`);\n\tconst content = `${JSON.stringify(compilerConfig, null, 2)}\\n`;\n\tif (check) {\n\t\tconst existing = await readFile(path, 'utf-8').catch(() => null);\n\t\treturn { name, path, status: existing === content ? 'up-to-date' : 'outdated' };\n\t}\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, content);\n\treturn { name, path, status: 'written' };\n}\n\n/** What `.stars/tsconfig.json` has always been: the bot sources, with the aliases and the bundler's compiler options. */\nfunction appTsconfig(config: ResolvedStarsConfig, name: TsconfigName) {\n\tconst path = join(config.root, '.stars', name === 'tsconfig' ? 'tsconfig.json' : `tsconfig.${name}.json`);\n\tconst fromGenerated = (target: string) => `./${relative(dirname(path), target).replaceAll('\\\\', '/')}`;\n\tconst paths: Record<string, string[]> = {};\n\tif (config.build.tool === 'tsdown') {\n\t\tconst source = dirname(config.entry);\n\t\tconst defaults = config.build.configFile === null ? { '~': source, '@': source, '~~': config.root, '@@': config.root } : {};\n\t\tconst aliases = { ...defaults, ...(config.tsdown.alias as Record<string, unknown> | undefined) };\n\t\tfor (const [alias, target] of Object.entries(aliases)) {\n\t\t\t// Module redirects belong to the bundler; only filesystem targets become TypeScript paths.\n\t\t\tif (typeof target !== 'string' || (!isAbsolute(target) && !/^\\.\\.?[/\\\\]/.test(target))) continue;\n\t\t\tconst resolved = fromGenerated(resolve(config.root, target));\n\t\t\tpaths[alias] = [resolved];\n\t\t\tpaths[`${alias}/*`] = [`${resolved}/*`];\n\t\t}\n\t} else if (config.experimental.enableNitro) {\n\t\t// `NitroBuilder` turns on Vite's native `resolve.tsconfigPaths` (see https://nitro.build/examples/import-alias),\n\t\t// which reads these `paths` straight out of the generated tsconfig — no `tsdown.alias` equivalent to merge\n\t\t// with, `vite`/Nitro projects only have this file's own `compilerOptions.paths` to extend or replace.\n\t\tconst source = dirname(config.entry);\n\t\tconst defaults = { '~': source, '@': source, '~~': config.root, '@@': config.root };\n\t\tfor (const [alias, target] of Object.entries(defaults)) {\n\t\t\tconst resolved = fromGenerated(resolve(config.root, target));\n\t\t\tpaths[alias] = [resolved];\n\t\t\tpaths[`${alias}/*`] = [`${resolved}/*`];\n\t\t}\n\t}\n\treturn {\n\t\tcompilerOptions: {\n\t\t\t...sapphireOptions,\n\t\t\t// Bundlers emit the application; TypeScript only checks it. Keep Node16 emit for tsc.\n\t\t\t...(config.build.tool === 'tsdown' || config.build.tool === 'vite'\n\t\t\t\t? {\n\t\t\t\t\t\tmodule: 'ESNext',\n\t\t\t\t\t\tmoduleResolution: 'Bundler',\n\t\t\t\t\t\tmoduleDetection: 'force',\n\t\t\t\t\t\tisolatedModules: true,\n\t\t\t\t\t\tverbatimModuleSyntax: true,\n\t\t\t\t\t\tallowJs: true,\n\t\t\t\t\t\tallowImportingTsExtensions: true,\n\t\t\t\t\t\tresolvePackageJsonImports: true,\n\t\t\t\t\t\tlib: ['ESNext', 'DOM'],\n\t\t\t\t\t\tnoEmit: true\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\ttarget: 'ES2022',\n\t\t\tforceConsistentCasingInFileNames: true,\n\t\t\tskipLibCheck: true,\n\t\t\ttsBuildInfoFile: name === 'tsconfig' ? './tsconfig.tsbuildinfo' : `./tsconfig.${name}.tsbuildinfo`,\n\t\t\tpaths\n\t\t},\n\t\tinclude: [`${fromGenerated(dirname(config.entry))}/**/*`, ...(config.imports.enabled ? [fromGenerated(config.imports.dts)] : [])],\n\t\texclude: [fromGenerated(join(config.root, 'node_modules')), fromGenerated(config.build.outDir)]\n\t};\n}\n\n/**\n * The root files that run in Node: `stars.config.*`, `vitest.config.*`, `scripts/**` and the like. No DOM, no\n * bundler resolution, no aliases or auto imports (none of it applies to a file Node loads itself), and no decorators.\n */\nfunction nodeTsconfig(config: ResolvedStarsConfig) {\n\tconst path = join(config.root, '.stars', 'tsconfig.node.json');\n\tconst fromGenerated = (target: string) => `./${relative(dirname(path), target).replaceAll('\\\\', '/')}`;\n\tconst source = dirname(config.entry);\n\tconst include = NODE_PROJECT_FILES.map((pattern) => fromGenerated(join(config.root, pattern)));\n\t// A custom `--config` file name is not covered by the globs.\n\tif (config.configFile !== null) include.push(fromGenerated(config.configFile));\n\n\treturn {\n\t\tcompilerOptions: {\n\t\t\t...strictOptions,\n\t\t\ttarget: 'ES2022',\n\t\t\tmodule: 'NodeNext',\n\t\t\tmoduleResolution: 'NodeNext',\n\t\t\tmoduleDetection: 'force',\n\t\t\tlib: ['ESNext'],\n\t\t\ttypes: ['node'],\n\t\t\tallowJs: true,\n\t\t\tnoEmit: true,\n\t\t\tforceConsistentCasingInFileNames: true,\n\t\t\tskipLibCheck: true,\n\t\t\ttsBuildInfoFile: './tsconfig.node.tsbuildinfo'\n\t\t},\n\t\tinclude: [...new Set(include)],\n\t\t// The two projects must not overlap: the bot sources belong to the app one. An entry in the project root has\n\t\t// no directory of its own to leave out.\n\t\texclude: [\n\t\t\tfromGenerated(join(config.root, 'node_modules')),\n\t\t\tfromGenerated(config.build.outDir),\n\t\t\t...(source === config.root ? [] : [fromGenerated(source)])\n\t\t]\n\t};\n}\n","import type { ResolvedStarsConfig } from '@wolfstar/schema';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { loadAutoImportsModule } from '../utils/framework-auto-imports.js';\nimport { formatError } from '../utils/errors.js';\nimport type { StarsHookable } from '../utils/hooks.js';\nimport { modulesPreloadWarning, prepareModulesPreload } from '../utils/modules.js';\nimport { findLegacyRootTsconfig, prepareTsconfig } from '../utils/tsconfig.js';\n\nexport type PrepareResult =\n\t| { enabled: false; dts: null; status: null }\n\t| { enabled: true; dts: string; status: 'written' | 'up-to-date' | 'outdated' };\n\n/**\n * Regenerates `imports.dts` unconditionally, used by `stars dev`/`stars build` before the first build so the\n * declaration file exists (and is current) even when the user never ran `stars prepare` themselves.\n */\nexport async function prepareAutoImports(config: ResolvedStarsConfig, check = false): Promise<PrepareResult> {\n\tif (!config.imports.enabled) return { enabled: false, dts: null, status: null };\n\n\tconst { dirs, presets, exclude, dts } = config.imports;\n\tconst { generateAutoImportsDts } = await loadAutoImportsModule(config.root);\n\tconst content = await generateAutoImportsDts({ root: config.root, dirs, presets, exclude });\n\n\tif (check) {\n\t\tconst existing = await readFile(dts, 'utf-8').catch(() => null);\n\t\treturn { enabled: true, dts, status: existing === content ? 'up-to-date' : 'outdated' };\n\t}\n\n\tawait mkdir(dirname(dts), { recursive: true });\n\tawait writeFile(dts, content);\n\treturn { enabled: true, dts, status: 'written' };\n}\n\n/** Prepares TypeScript configuration and auto import declarations before building. */\nexport async function prepareProject(config: ResolvedStarsConfig, hooks?: StarsHookable, check = false) {\n\tawait hooks?.callHook('prepare:before', config);\n\tconst tsconfigs = await prepareTsconfig(config, check);\n\tconst result = {\n\t\t...(await prepareAutoImports(config, check)),\n\t\t// The app config from version 6, the single file below it; `tsconfigs` has every generated file.\n\t\ttsconfig: tsconfigs[0]!,\n\t\ttsconfigs,\n\t\tmodules: await prepareModulesPreload(config, check)\n\t};\n\tawait hooks?.callHook('prepare:done', config, { dts: result.dts, status: result.status });\n\treturn result;\n}\n\n/**\n * Prints the non-fatal configuration diagnostics (e.g. an end-of-life compatibility version, or a root `tsconfig.json` still extending `.stars/tsconfig.json`), and, unless `production`\n * is `false` (`stars dev`, which does that itself), what a production start of the project still needs to do.\n */\nexport async function reportWarnings(\n\tconfig: ResolvedStarsConfig,\n\twrite: (text: string) => void,\n\toptions: { production?: boolean } = {}\n): Promise<void> {\n\tfor (const warning of config.warnings) write(await formatError(warning));\n\n\tconst legacyTsconfig = await findLegacyRootTsconfig(config);\n\tif (legacyTsconfig) write(await formatError(legacyTsconfig));\n\n\tconst preload = options.production === false ? null : modulesPreloadWarning(config);\n\tif (preload) write(await formatError(preload));\n}\n"],"mappings":"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"}
@@ -1,240 +1,7 @@
1
- import { t as findInstalledVersion } from "./project-zlQStwsf.js";
2
- import { i as readProjectEnv } from "./hooks-Cy6F2bhH.js";
1
+ import { t as findInstalledVersion } from "./project-DBqQMFCk.js";
3
2
  import { pathToFileURL } from "node:url";
4
- import { EventEmitter } from "node:events";
5
3
  import { resolveSync } from "mlly";
6
- import { startTunnel } from "untun";
7
4
 
8
- //#region src/dev/tunnel.ts
9
- const SIGNALS = [
10
- "SIGINT",
11
- "SIGTERM",
12
- "SIGHUP"
13
- ];
14
- function isCloudflaredExit(reason) {
15
- return reason instanceof Error && reason.message.startsWith("cloudflared exited (");
16
- }
17
- /**
18
- * Exposes the bot's interactions endpoint publicly while `stars dev` runs.
19
- *
20
- * `dev.tunnel: true` opens a `cloudflared` quick tunnel via `untun` (it changes on every run); a configured https
21
- * URL is only probed, since the user already serves it. Writing the URL to the Discord application is opt-in
22
- * through `dev.tunnel.updateEndpoint`, because it edits a live application.
23
- */
24
- var Tunnel = class extends EventEmitter {
25
- config;
26
- #startTunnel;
27
- #tunnel = null;
28
- #state = "off";
29
- #url = null;
30
- #wanted = false;
31
- #rejectionGuard = null;
32
- constructor(config, options = {}) {
33
- super();
34
- this.config = config;
35
- this.#startTunnel = options.startTunnel ?? startTunnel;
36
- }
37
- get state() {
38
- return this.#state;
39
- }
40
- /** The public URL of the interactions endpoint, `null` until the tunnel is up. */
41
- get url() {
42
- return this.#url;
43
- }
44
- async start(forceQuick = false) {
45
- if (this.#state === "starting" || this.#state === "up") return;
46
- const configured = this.config.dev.tunnel;
47
- const tunnel = forceQuick && configured.mode === "off" ? {
48
- mode: "quick",
49
- path: "/",
50
- updateEndpoint: false
51
- } : configured;
52
- if (tunnel.mode === "off") return;
53
- this.#wanted = true;
54
- this.#setState("starting", null);
55
- const url = tunnel.mode === "url" ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();
56
- if (!url || !this.#wanted) return;
57
- this.#setState("up", endpointUrl(url, tunnel.path));
58
- this.emit("log", "success", `Tunnel ready at ${this.#url}`);
59
- if (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url);
60
- }
61
- /** Toggles the configured tunnel, opening a quick tunnel when the project did not configure one. */
62
- toggle() {
63
- return this.#state === "starting" || this.#state === "up" ? this.close() : this.start(true);
64
- }
65
- async close() {
66
- this.#wanted = false;
67
- const tunnel = this.#tunnel;
68
- this.#tunnel = null;
69
- this.#setState("off", null);
70
- if (!tunnel) return;
71
- try {
72
- await tunnel.close();
73
- } finally {
74
- this.#releaseRejectionGuard();
75
- }
76
- }
77
- async #useConfiguredUrl(tunnel) {
78
- 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"}`);
79
- return tunnel.url;
80
- }
81
- async #openQuickTunnel() {
82
- const target = this.config.dev.url;
83
- if (!target) {
84
- this.emit("log", "error", "A quick tunnel needs `dev.url` to know what to forward to");
85
- this.#setState("failed", null);
86
- return null;
87
- }
88
- this.emit("log", "info", "Opening a cloudflared quick tunnel…");
89
- this.#installRejectionGuard();
90
- try {
91
- const tunnel = await this.#startWithoutSignalHandlers(target);
92
- if (!this.#wanted) {
93
- await tunnel?.close();
94
- this.#releaseRejectionGuard();
95
- return null;
96
- }
97
- if (!tunnel) {
98
- this.emit("log", "error", "cloudflared setup was cancelled");
99
- this.#setState("failed", null);
100
- this.#releaseRejectionGuard();
101
- return null;
102
- }
103
- this.#tunnel = tunnel;
104
- return await tunnel.getURL();
105
- } catch (error) {
106
- this.#releaseRejectionGuard();
107
- if (!this.#wanted) return null;
108
- this.emit("log", "error", `cloudflared failed: ${error instanceof Error ? error.message : String(error)}`);
109
- this.#setState("failed", null);
110
- return null;
111
- }
112
- }
113
- /**
114
- * `untun` adds its own `SIGINT`/`SIGTERM`/`SIGHUP` listeners that call `process.exit()` once cloudflared is closed,
115
- * which races `stars dev`'s graceful shutdown (it closes the tunnel through {@link close} anyway), so they are removed.
116
- */
117
- async #startWithoutSignalHandlers(target) {
118
- const before = SIGNALS.map((signal) => new Set(process.listeners(signal)));
119
- try {
120
- return await this.#startTunnel({
121
- url: target,
122
- acceptCloudflareNotice: true
123
- });
124
- } finally {
125
- SIGNALS.forEach((signal, index) => {
126
- for (const listener of process.listeners(signal)) if (!before[index].has(listener)) process.off(signal, listener);
127
- });
128
- }
129
- }
130
- /**
131
- * `untun` rejects a promise per connection when cloudflared exits and never handles it (and its location pattern
132
- * does not match lowercase codes such as `mxp03`, so that promise is never settled before), which Node reports as an
133
- * unhandled rejection that ends the process, whenever the tunnel closes. The guard swallows that one error for as
134
- * long as this tunnel exists; anything else it sees is thrown again, as if it was not installed.
135
- */
136
- #installRejectionGuard() {
137
- if (this.#rejectionGuard) return;
138
- const guard = (reason) => {
139
- if (!isCloudflaredExit(reason)) {
140
- if (process.listeners("unhandledRejection").length === 1) throw reason;
141
- return;
142
- }
143
- if (this.#state !== "up" || !this.#wanted) return;
144
- this.#tunnel = null;
145
- this.emit("log", "error", `cloudflared stopped unexpectedly: ${reason.message}`);
146
- this.#setState("failed", null);
147
- this.#releaseRejectionGuard();
148
- };
149
- this.#rejectionGuard = guard;
150
- process.on("unhandledRejection", guard);
151
- }
152
- /** Node reports an unhandled rejection after the microtasks that follow it, so the guard has to outlive them. */
153
- #releaseRejectionGuard() {
154
- const guard = this.#rejectionGuard;
155
- this.#rejectionGuard = null;
156
- if (guard) setImmediate(() => process.off("unhandledRejection", guard));
157
- }
158
- /**
159
- * Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a
160
- * signed ping before accepting it, so the bot has to be up already — this runs after the first successful build.
161
- */
162
- async #updateInteractionsEndpoint(url) {
163
- const credentials = readDiscordCredentials(this.config);
164
- if (!credentials) {
165
- this.emit("log", "warn", "Skipped the interactions endpoint update: DISCORD_TOKEN is not set");
166
- return;
167
- }
168
- try {
169
- const response = await fetch("https://discord.com/api/v10/applications/@me", {
170
- method: "PATCH",
171
- headers: {
172
- authorization: `Bot ${credentials.token}`,
173
- "content-type": "application/json"
174
- },
175
- body: JSON.stringify({ interactions_endpoint_url: url }),
176
- signal: AbortSignal.timeout(1e4)
177
- });
178
- if (response.ok) this.emit("log", "success", `Interactions endpoint set to ${url}`);
179
- else this.emit("log", "error", `Discord rejected the interactions endpoint (${response.status}): ${(await response.text()).slice(0, 300)}`);
180
- } catch (error) {
181
- this.emit("log", "error", `Failed to update the interactions endpoint: ${error instanceof Error ? error.message : String(error)}`);
182
- }
183
- }
184
- #setState(state, url) {
185
- this.#state = state;
186
- this.#url = url;
187
- this.emit("state", state, url);
188
- }
189
- };
190
- /**
191
- * Joins the tunnel's origin with the path the interactions endpoint is served on.
192
- */
193
- function endpointUrl(origin, path) {
194
- return path === "/" ? origin : new URL(path, origin.endsWith("/") ? origin : `${origin}/`).href;
195
- }
196
- async function probe(url) {
197
- try {
198
- return (await fetch(url, {
199
- method: "HEAD",
200
- signal: AbortSignal.timeout(5e3)
201
- })).status < 500;
202
- } catch {
203
- return false;
204
- }
205
- }
206
- /**
207
- * Reads the Discord credentials the CLI needs from the environment, falling back to the project's `.env` files the
208
- * way the bot itself does once it starts.
209
- */
210
- function readDiscordCredentials(config, env = process.env) {
211
- const fromFiles = readProjectEnvFilesCached(config);
212
- const read = (...keys) => {
213
- for (const key of keys) {
214
- const value = config.dev.env[key] ?? env[key] ?? fromFiles[key];
215
- if (value) return value;
216
- }
217
- return null;
218
- };
219
- const token = read("DISCORD_TOKEN", "TOKEN");
220
- if (!token) return null;
221
- return {
222
- token,
223
- applicationId: read("DISCORD_APPLICATION_ID", "APPLICATION_ID", "DISCORD_CLIENT_ID", "CLIENT_ID")
224
- };
225
- }
226
- let envFileCache = null;
227
- /** The variables the bot loads (see {@link readProjectEnv}), once per project and `env` options. */
228
- function readProjectEnvFilesCached(config) {
229
- const key = JSON.stringify([config.root, config.env.options]);
230
- if (envFileCache?.key !== key) envFileCache = {
231
- key,
232
- values: readProjectEnv(config)
233
- };
234
- return envFileCache.values;
235
- }
236
-
237
- //#endregion
238
5
  //#region src/dev/bridge.ts
239
6
  /** Marks a message of the bridge, so a bot using the IPC channel for something else is left alone. */
240
7
  const BRIDGE_SOURCE = "stars:bridge";
@@ -467,5 +234,5 @@ function isAtLeast(version, minimum) {
467
234
  }
468
235
 
469
236
  //#endregion
470
- 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 };
471
- //# sourceMappingURL=bridge-BegCt_n2.js.map
237
+ export { isAtLeast as a, bridgeImportArgs as i, CLI_SOURCE as n, parseBridgeMessage as o, DUMP_ENV as r, BRIDGE_FRAMEWORK_VERSION as t };
238
+ //# sourceMappingURL=bridge-Cbm0jX9h.js.map