@wolfstar/cli 2.2.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.
- package/LICENSE +201 -201
- package/README.md +465 -304
- package/dist/{_shared-DJrpalNQ.js → _shared-BIQK0Bhm.js} +2 -2
- package/dist/_shared-BIQK0Bhm.js.map +1 -0
- package/dist/bridge-BvkmlAOZ.js +410 -0
- package/dist/bridge-BvkmlAOZ.js.map +1 -0
- package/dist/{build-CluOR8dJ.js → build-DJcc95Yq.js} +5 -5
- package/dist/build-DJcc95Yq.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{codegen-D6V1i_fa.js → codegen-aJSyPz-J.js} +4 -4
- package/dist/codegen-aJSyPz-J.js.map +1 -0
- package/dist/command-diff-BK912Hjc.js +78 -0
- package/dist/command-diff-BK912Hjc.js.map +1 -0
- package/dist/commands-CIPWQtF0.js +3 -0
- package/dist/commands-Clpbqq3K.js +15 -0
- package/dist/commands-Clpbqq3K.js.map +1 -0
- package/dist/{commands-BJ589_AC.js → commands-DVC7ky6f.js} +259 -9
- package/dist/commands-DVC7ky6f.js.map +1 -0
- package/dist/completions-D2KYtrrv.js +161 -0
- package/dist/completions-D2KYtrrv.js.map +1 -0
- package/dist/{dev-DJJZN4NJ.js → dev-oPPVR7d_.js} +434 -28
- package/dist/dev-oPPVR7d_.js.map +1 -0
- package/dist/{diagnostics-3JRAy8Xj.js → diagnostics-BysbNnPD.js} +22 -1
- package/dist/diagnostics-BysbNnPD.js.map +1 -0
- package/dist/doctor-DkVUwgDd.js +334 -0
- package/dist/doctor-DkVUwgDd.js.map +1 -0
- package/dist/errors-COFWKKKw.js.map +1 -1
- package/dist/external-CxFKxwi3.js.map +1 -1
- package/dist/framework-auto-imports-Gu_PXSyr.js.map +1 -1
- package/dist/{hooks-BfDFM38k.js → hooks-Cy6F2bhH.js} +20 -4
- package/dist/hooks-Cy6F2bhH.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{info-C1Kx_4Og.js → info-CKJVMWIT.js} +15 -4
- package/dist/info-CKJVMWIT.js.map +1 -0
- package/dist/{locales-BzOLRBVg.js → locales-BJAP4HYf.js} +5 -5
- package/dist/locales-BJAP4HYf.js.map +1 -0
- package/dist/log-view-B7uJEnMR.js +342 -0
- package/dist/log-view-B7uJEnMR.js.map +1 -0
- package/dist/{nitro-yjqZBh5L.js → nitro-B8u1iVgf.js} +2 -2
- package/dist/nitro-B8u1iVgf.js.map +1 -0
- package/dist/none-uC_wi_l4.js.map +1 -1
- package/dist/output-mode-Bp7Da8vJ.js.map +1 -1
- package/dist/{plain-B6z85uQP.js → plain-DG7cy0Kk.js} +7 -2
- package/dist/plain-DG7cy0Kk.js.map +1 -0
- package/dist/{prepare-Cb45Z4re.js → prepare-BM93i3Rm.js} +4 -4
- package/dist/prepare-BM93i3Rm.js.map +1 -0
- package/dist/{project-D-8VIcyF.js → project-zlQStwsf.js} +2 -2
- package/dist/project-zlQStwsf.js.map +1 -0
- package/dist/{run-BiLZqib3.js → run-DMM2YlP3.js} +2 -12
- package/dist/run-DMM2YlP3.js.map +1 -0
- package/dist/{theme-3Fc_LI-2.js → theme-yS2h5dd_.js} +25 -1
- package/dist/theme-yS2h5dd_.js.map +1 -0
- package/dist/tsc-C5l5TQGC.js +3 -0
- package/dist/{tsc-DEBcwIO0.js → tsc-puVAcwmo.js} +24 -5
- package/dist/tsc-puVAcwmo.js.map +1 -0
- package/dist/{tsdown-DrWXld54.js → tsdown-Dwt3JhPw.js} +2 -2
- package/dist/tsdown-Dwt3JhPw.js.map +1 -0
- package/dist/{tui-BRddrLQP.js → tui-nldGSD4c.js} +913 -141
- package/dist/tui-nldGSD4c.js.map +1 -0
- package/dist/version-BzclCE19.js.map +1 -1
- package/dist/{vite-Bf7nrJx7.js → vite-YZUtv2Hk.js} +2 -2
- package/dist/vite-YZUtv2Hk.js.map +1 -0
- package/package.json +5 -5
- package/dist/_shared-DJrpalNQ.js.map +0 -1
- package/dist/build-CluOR8dJ.js.map +0 -1
- package/dist/codegen-D6V1i_fa.js.map +0 -1
- package/dist/commands-BJ589_AC.js.map +0 -1
- package/dist/dev-DJJZN4NJ.js.map +0 -1
- package/dist/diagnostics-3JRAy8Xj.js.map +0 -1
- package/dist/hooks-BfDFM38k.js.map +0 -1
- package/dist/info-C1Kx_4Og.js.map +0 -1
- package/dist/locales-BzOLRBVg.js.map +0 -1
- package/dist/log-buffer-CY82aqnb.js +0 -72
- package/dist/log-buffer-CY82aqnb.js.map +0 -1
- package/dist/nitro-yjqZBh5L.js.map +0 -1
- package/dist/plain-B6z85uQP.js.map +0 -1
- package/dist/prepare-Cb45Z4re.js.map +0 -1
- package/dist/project-D-8VIcyF.js.map +0 -1
- package/dist/run-BiLZqib3.js.map +0 -1
- package/dist/theme-3Fc_LI-2.js.map +0 -1
- package/dist/tsc-B931Ezh7.js +0 -3
- package/dist/tsc-DEBcwIO0.js.map +0 -1
- package/dist/tsdown-DrWXld54.js.map +0 -1
- package/dist/tui-BRddrLQP.js.map +0 -1
- package/dist/tunnel-DHSxOZKr.js +0 -175
- package/dist/tunnel-DHSxOZKr.js.map +0 -1
- package/dist/vite-Bf7nrJx7.js.map +0 -1
|
@@ -1,22 +1,97 @@
|
|
|
1
1
|
import { i as shouldUseColor, n as prefersReducedMotion, r as resolveOutputMode } from "./output-mode-Bp7Da8vJ.js";
|
|
2
2
|
import { i as renderCrashReport, t as ExitCode } from "./errors-COFWKKKw.js";
|
|
3
|
-
import { t as cliDiagnostics } from "./diagnostics-
|
|
4
|
-
import { a as readSavedTheme, c as saveTheme, i as isThemeSetting, n as THEME_SETTINGS, s as resolveThemeSetting } from "./theme-
|
|
5
|
-
import { r as resolveBinary } from "./project-
|
|
6
|
-
import { i as createLineSplitter, n as resolveTscBinary, r as ProcessSupervisor } from "./tsc-
|
|
7
|
-
import { n as createBuilder, t as Locales } from "./locales-
|
|
8
|
-
import { c as moduleImportArgs, l as
|
|
9
|
-
import { n as reportWarnings, t as prepareProject } from "./_shared-
|
|
10
|
-
import { t as
|
|
11
|
-
import { n as
|
|
3
|
+
import { t as cliDiagnostics } from "./diagnostics-BysbNnPD.js";
|
|
4
|
+
import { a as readSavedTheme, c as saveTheme, i as isThemeSetting, n as THEME_SETTINGS, s as resolveThemeSetting } from "./theme-yS2h5dd_.js";
|
|
5
|
+
import { r as resolveBinary } from "./project-zlQStwsf.js";
|
|
6
|
+
import { i as createLineSplitter, n as resolveTscBinary, r as ProcessSupervisor } from "./tsc-puVAcwmo.js";
|
|
7
|
+
import { n as createBuilder, t as Locales } from "./locales-BJAP4HYf.js";
|
|
8
|
+
import { c as moduleImportArgs, d as resolveCwd, l as collectFlag, n as loadProject, r as withProjectEnv, s as envImportArgs, t as applyEnvOptions, u as projectArgs } from "./hooks-Cy6F2bhH.js";
|
|
9
|
+
import { n as reportWarnings, t as prepareProject } from "./_shared-BIQK0Bhm.js";
|
|
10
|
+
import { r as diffSnapshots, t as describeCommand } from "./command-diff-BK912Hjc.js";
|
|
11
|
+
import { i as bridgeImportArgs, n as CLI_SOURCE, o as parseBridgeMessage, s as Tunnel } from "./bridge-BvkmlAOZ.js";
|
|
12
|
+
import { a as initialLogView, s as isLogLevel, x as classifyAppLine, y as LogBuffer } from "./log-view-B7uJEnMR.js";
|
|
12
13
|
import { defineCommand } from "citty";
|
|
13
|
-
import { createWriteStream, mkdirSync } from "node:fs";
|
|
14
|
-
import { dirname } from "node:path";
|
|
14
|
+
import { createWriteStream, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
15
|
+
import { basename, dirname, extname, join, sep } from "node:path";
|
|
15
16
|
import { displayPath } from "@wolfstar/schema";
|
|
16
17
|
import { EventEmitter } from "node:events";
|
|
17
18
|
import { spawn } from "node:child_process";
|
|
19
|
+
import { stripVTControlCharacters } from "node:util";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
18
21
|
import { lookup } from "node:dns/promises";
|
|
19
22
|
|
|
23
|
+
//#region src/dev/changed-files.ts
|
|
24
|
+
const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
25
|
+
/**
|
|
26
|
+
* `tsc -b` rewrites its build info on every build, whatever changed; a source map only ever changes with the file it
|
|
27
|
+
* maps, and is most of what there would be to read.
|
|
28
|
+
*/
|
|
29
|
+
const SKIPPED_FILES = /\.(?:tsbuildinfo|map)$/;
|
|
30
|
+
/**
|
|
31
|
+
* Hashes every file under `roots`, skipping `node_modules`, `.git` and everything under `exclude`.
|
|
32
|
+
*
|
|
33
|
+
* The builders only report that a build finished, and a bundler rewrites files it did not change (`tsdown` writes
|
|
34
|
+
* its whole output on every rebuild), so neither an event nor a modification time says what a build changed: two
|
|
35
|
+
* snapshots do (see {@link changedFiles}).
|
|
36
|
+
*/
|
|
37
|
+
function snapshotFiles(roots, exclude = [], cache = /* @__PURE__ */ new Map()) {
|
|
38
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
39
|
+
const visit = (directory) => {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(directory, { withFileTypes: true });
|
|
43
|
+
} catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const path = join(directory, entry.name);
|
|
48
|
+
if (exclude.some((excluded) => isInside(path, excluded))) continue;
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) visit(path);
|
|
51
|
+
} else if (entry.isFile() && !SKIPPED_FILES.test(entry.name)) try {
|
|
52
|
+
const { size, mtimeMs } = statSync(path);
|
|
53
|
+
const known = cache.get(path);
|
|
54
|
+
const hash = known?.size === size && known.mtimeMs === mtimeMs ? known.hash : createHash("sha1").update(readFileSync(path)).digest("base64");
|
|
55
|
+
cache.set(path, {
|
|
56
|
+
size,
|
|
57
|
+
mtimeMs,
|
|
58
|
+
hash
|
|
59
|
+
});
|
|
60
|
+
snapshot.set(path, hash);
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
for (const root of new Set(roots)) visit(root);
|
|
65
|
+
return snapshot;
|
|
66
|
+
}
|
|
67
|
+
/** The files that were added, removed, or whose content differs between two snapshots. */
|
|
68
|
+
function changedFiles(previous, next) {
|
|
69
|
+
const changed = [...next].filter(([path, hash]) => previous.get(path) !== hash).map(([path]) => path);
|
|
70
|
+
for (const path of previous.keys()) if (!next.has(path)) changed.push(path);
|
|
71
|
+
return changed;
|
|
72
|
+
}
|
|
73
|
+
const PIECE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
74
|
+
".js",
|
|
75
|
+
".mjs",
|
|
76
|
+
".cjs",
|
|
77
|
+
".ts",
|
|
78
|
+
".mts",
|
|
79
|
+
".cts"
|
|
80
|
+
]);
|
|
81
|
+
/**
|
|
82
|
+
* Whether a file a store has not loaded yet could be a piece, by the rule the stores filter files with: a script
|
|
83
|
+
* that is not a declaration file and whose name does not start with `_`.
|
|
84
|
+
*/
|
|
85
|
+
function couldBePiece(path) {
|
|
86
|
+
const extension = extname(path);
|
|
87
|
+
return PIECE_EXTENSIONS.has(extension) && !path.endsWith(".d.ts") && !basename(path, extension).startsWith("_");
|
|
88
|
+
}
|
|
89
|
+
/** Whether `path` is `directory` itself or lies under it. */
|
|
90
|
+
function isInside(path, directory) {
|
|
91
|
+
return path === directory || path.startsWith(directory.endsWith(sep) ? directory : `${directory}${sep}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
20
95
|
//#region src/dev/typechecker.ts
|
|
21
96
|
const WATCH_START = /Starting (incremental )?compilation/;
|
|
22
97
|
const WATCH_DONE = /Found (\d+) errors?\. Watching for file changes/;
|
|
@@ -232,6 +307,8 @@ var DevService = class extends EventEmitter {
|
|
|
232
307
|
typechecker;
|
|
233
308
|
tunnel;
|
|
234
309
|
locales;
|
|
310
|
+
/** Whether something can answer a {@link DevPrompt}: the interactive UI sets it, the plain renderer cannot. */
|
|
311
|
+
promptable = false;
|
|
235
312
|
#hooks;
|
|
236
313
|
/** Runs hooks one at a time, so an async `build:done` settles before the next build's hooks or a restart. */
|
|
237
314
|
#hookChain = Promise.resolve();
|
|
@@ -252,6 +329,23 @@ var DevService = class extends EventEmitter {
|
|
|
252
329
|
startedAt: Date.now(),
|
|
253
330
|
readyMs: null
|
|
254
331
|
};
|
|
332
|
+
#ready = false;
|
|
333
|
+
#port = null;
|
|
334
|
+
/** The store paths the running bot watches for hot reloads, empty when it does not. */
|
|
335
|
+
#hmrPaths = [];
|
|
336
|
+
#paused = false;
|
|
337
|
+
#prompt = null;
|
|
338
|
+
/** The commands the bot last reported, kept across restarts: a restart is how most changes arrive. */
|
|
339
|
+
#commands = null;
|
|
340
|
+
/** What the build output held after the previous build, `null` before the first one. */
|
|
341
|
+
#files = null;
|
|
342
|
+
#hashes = /* @__PURE__ */ new Map();
|
|
343
|
+
/** The files the running bot loaded pieces from: what its hot reload can replace. */
|
|
344
|
+
#pieces = /* @__PURE__ */ new Set();
|
|
345
|
+
/** The commands Discord is assumed to have: the first report of the session, then whatever was last deployed. */
|
|
346
|
+
#deployed = null;
|
|
347
|
+
/** A refresh of the commands asked for while the bot was not listening; sent once it is. */
|
|
348
|
+
#refreshPending = false;
|
|
255
349
|
constructor(config, options) {
|
|
256
350
|
super();
|
|
257
351
|
this.config = config;
|
|
@@ -292,6 +386,15 @@ var DevService = class extends EventEmitter {
|
|
|
292
386
|
});
|
|
293
387
|
this.supervisor.on("stdout", (line) => this.log("app", classifyAppLine(line, "info"), line));
|
|
294
388
|
this.supervisor.on("stderr", (line) => this.log("app", classifyAppLine(line, "error"), line));
|
|
389
|
+
this.supervisor.on("message", (message) => {
|
|
390
|
+
const parsed = parseBridgeMessage(message);
|
|
391
|
+
if (parsed === null) return;
|
|
392
|
+
try {
|
|
393
|
+
this.#onBridge(parsed);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
this.log("stars", "warn", `Ignored a ${parsed.type} message from the bot: ${error instanceof Error ? error.message : String(error)}`);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
295
398
|
this.supervisor.on("error", (error) => this.log("stars", "error", `Failed to start the bot: ${error.message}`));
|
|
296
399
|
this.supervisor.on("exit", (exit) => this.#onExit(exit));
|
|
297
400
|
this.typechecker.on("log", (level, text) => this.log("tsc", level, text));
|
|
@@ -324,14 +427,20 @@ var DevService = class extends EventEmitter {
|
|
|
324
427
|
typecheck: this.typechecker.state,
|
|
325
428
|
typeErrors: this.typechecker.errors,
|
|
326
429
|
tunnel: this.tunnel.state,
|
|
327
|
-
tunnelUrl: this.tunnel.url
|
|
430
|
+
tunnelUrl: this.tunnel.url,
|
|
431
|
+
ready: this.#ready,
|
|
432
|
+
port: this.#port,
|
|
433
|
+
hmr: this.#hmrPaths.length > 0,
|
|
434
|
+
paused: this.#paused,
|
|
435
|
+
prompt: this.#prompt
|
|
328
436
|
};
|
|
329
437
|
}
|
|
330
|
-
log(source, level, text) {
|
|
438
|
+
log(source, level, text, extra = {}) {
|
|
331
439
|
this.logs.push({
|
|
332
440
|
source,
|
|
333
441
|
level,
|
|
334
|
-
text
|
|
442
|
+
text,
|
|
443
|
+
...extra
|
|
335
444
|
});
|
|
336
445
|
}
|
|
337
446
|
/**
|
|
@@ -374,10 +483,51 @@ var DevService = class extends EventEmitter {
|
|
|
374
483
|
message: "starting the bot"
|
|
375
484
|
};
|
|
376
485
|
this.#health = "unknown";
|
|
486
|
+
this.#paused = false;
|
|
377
487
|
this.supervisor.start();
|
|
378
488
|
this.#emitStatus();
|
|
379
489
|
});
|
|
380
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* Stops the bot and leaves it stopped (the `d` key): builds keep running, but nothing starts the bot again until
|
|
493
|
+
* {@link restart}. For when the bot must be off without ending the session.
|
|
494
|
+
*/
|
|
495
|
+
disconnect() {
|
|
496
|
+
this.#clearRestartTimer();
|
|
497
|
+
return this.#enqueue(async () => {
|
|
498
|
+
if (this.#stopped || this.#paused) return;
|
|
499
|
+
this.#paused = true;
|
|
500
|
+
this.log("stars", "info", "Stopped the bot, press r to start it again");
|
|
501
|
+
await this.supervisor.stop();
|
|
502
|
+
this.#emitStatus();
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
/** Settles the pending {@link DevPrompt}: `true` does what it asks, `false` dismisses it. */
|
|
506
|
+
answerPrompt(accept) {
|
|
507
|
+
if (this.#prompt === null) return;
|
|
508
|
+
this.#prompt = null;
|
|
509
|
+
if (accept) this.refreshCommands();
|
|
510
|
+
else this.log("stars", "info", "Kept the deployed commands as they are", { channel: "commands" });
|
|
511
|
+
this.#emitStatus();
|
|
512
|
+
}
|
|
513
|
+
/** Asks the bot to register its commands with Discord again. The bot answers on the `commands` channel. */
|
|
514
|
+
refreshCommands() {
|
|
515
|
+
if (!this.#ready) {
|
|
516
|
+
this.#refreshPending = true;
|
|
517
|
+
const when = this.supervisor.running ? "once the bot is ready" : "when the bot starts again";
|
|
518
|
+
this.log("stars", "info", `Refreshing commands ${when}…`, { channel: "commands" });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
this.#refreshPending = false;
|
|
522
|
+
const current = this.#commands?.guilds ?? {};
|
|
523
|
+
const request = {
|
|
524
|
+
source: CLI_SOURCE,
|
|
525
|
+
type: "commands:refresh",
|
|
526
|
+
clearGuilds: Object.keys(this.#deployed?.guilds ?? {}).filter((guild) => !Object.hasOwn(current, guild))
|
|
527
|
+
};
|
|
528
|
+
if (this.supervisor.send(request)) this.log("stars", "info", "Refreshing commands…", { channel: "commands" });
|
|
529
|
+
else this.log("stars", "warn", "Could not reach the bot to refresh its commands", { channel: "commands" });
|
|
530
|
+
}
|
|
381
531
|
/** Opens or closes the public tunnel (used by the `t` key). */
|
|
382
532
|
toggleTunnel() {
|
|
383
533
|
return this.tunnel.toggle();
|
|
@@ -414,6 +564,7 @@ var DevService = class extends EventEmitter {
|
|
|
414
564
|
this.logs.clear();
|
|
415
565
|
}
|
|
416
566
|
#onBuildSuccess(outcome) {
|
|
567
|
+
const hot = this.#hotReloadable();
|
|
417
568
|
try {
|
|
418
569
|
this.locales.copy();
|
|
419
570
|
} catch (error) {
|
|
@@ -435,8 +586,35 @@ var DevService = class extends EventEmitter {
|
|
|
435
586
|
if (this.#stopped) return;
|
|
436
587
|
this.typechecker.check();
|
|
437
588
|
this.log("stars", "success", this.builder.tool === "none" ? "Sources changed" : `Build succeeded in ${outcome.durationMs}ms`);
|
|
589
|
+
if (this.#paused) return;
|
|
590
|
+
if (hot !== null) {
|
|
591
|
+
for (const file of hot) this.log("stars", "trace", `UPDATE ${displayPath(this.config.root, file)}`, { channel: "hmr" });
|
|
592
|
+
this.#settleProgress();
|
|
593
|
+
this.#emitStatus();
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
438
596
|
this.#scheduleRestart(this.supervisor.state === "idle" ? "initial" : "build");
|
|
439
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* The files the build rewrote when all of them are pieces the running bot hot reloads, `null` when the bot has to
|
|
600
|
+
* be restarted instead: it is not running, it does not hot reload, `dev.hmr` is off, nothing changed (a rebuild for
|
|
601
|
+
* a reason that cannot be seen from the output), or a file changed that the bot cannot replace. What a build changed
|
|
602
|
+
* is the difference in content with the previous output, since a bundler rewrites files it did not change.
|
|
603
|
+
*
|
|
604
|
+
* The bot replaces the files it loaded pieces from, and loads a new file that could be one. A helper module next to
|
|
605
|
+
* the pieces (`_shared.js`, or any file no piece came from) is imported once and stays as it was: changing it
|
|
606
|
+
* needs a restart, even though it lies in a store path.
|
|
607
|
+
*/
|
|
608
|
+
#hotReloadable() {
|
|
609
|
+
if (this.config.dev.hmr === false) return null;
|
|
610
|
+
const roots = this.config.build.tool === "none" ? this.config.dev.watch : [this.config.build.outDir];
|
|
611
|
+
const previous = this.#files;
|
|
612
|
+
this.#files = snapshotFiles(roots, [this.locales.destination], this.#hashes);
|
|
613
|
+
if (previous === null || this.#hmrPaths.length === 0 || this.supervisor.state !== "running") return null;
|
|
614
|
+
const changed = changedFiles(previous, this.#files);
|
|
615
|
+
const replaceable = (file) => this.#hmrPaths.some((path) => isInside(file, path)) && (this.#pieces.has(file) || !previous.has(file) && couldBePiece(file));
|
|
616
|
+
return changed.length > 0 && changed.every(replaceable) ? changed : null;
|
|
617
|
+
}
|
|
440
618
|
#onBuildFailure(outcome) {
|
|
441
619
|
this.#lastBuild = outcome;
|
|
442
620
|
this.#queueHook("build:done", outcome, this.config);
|
|
@@ -447,6 +625,10 @@ var DevService = class extends EventEmitter {
|
|
|
447
625
|
#onExit(exit) {
|
|
448
626
|
this.#lastExit = exit;
|
|
449
627
|
this.#health = "unknown";
|
|
628
|
+
this.#ready = false;
|
|
629
|
+
this.#port = null;
|
|
630
|
+
this.#hmrPaths = [];
|
|
631
|
+
this.#pieces.clear();
|
|
450
632
|
if (!exit.requested) {
|
|
451
633
|
const how = exit.signal ? `signal ${exit.signal}` : `code ${exit.code}`;
|
|
452
634
|
this.log("stars", exit.code === 0 ? "warn" : "error", `The bot exited with ${how}, waiting for changes (press r to restart)`);
|
|
@@ -454,6 +636,7 @@ var DevService = class extends EventEmitter {
|
|
|
454
636
|
this.#emitStatus();
|
|
455
637
|
}
|
|
456
638
|
#scheduleRestart(reason) {
|
|
639
|
+
if (this.#paused) return;
|
|
457
640
|
this.#pendingReason = reason;
|
|
458
641
|
this.#clearRestartTimer();
|
|
459
642
|
this.#restartTimer = setTimeout(() => {
|
|
@@ -502,6 +685,141 @@ var DevService = class extends EventEmitter {
|
|
|
502
685
|
readyMs: this.#progress.readyMs ?? Date.now() - this.#progress.startedAt
|
|
503
686
|
};
|
|
504
687
|
}
|
|
688
|
+
/** Turns what the bot reports over the bridge into log entries and status. */
|
|
689
|
+
#onBridge(message) {
|
|
690
|
+
const relative = (path) => displayPath(this.config.root, path);
|
|
691
|
+
switch (message.type) {
|
|
692
|
+
case "ready":
|
|
693
|
+
this.#ready = true;
|
|
694
|
+
this.#port = message.port;
|
|
695
|
+
this.log("stars", "success", message.port === null ? "The bot is ready" : `Listening on port ${message.port}`, { channel: "lifecycle" });
|
|
696
|
+
if (this.#refreshPending) this.refreshCommands();
|
|
697
|
+
this.#emitStatus();
|
|
698
|
+
return;
|
|
699
|
+
case "pieces":
|
|
700
|
+
for (const piece of message.pieces) this.#pieces.add(piece.path);
|
|
701
|
+
if (message.pieces.length === 0) return;
|
|
702
|
+
this.log("stars", "debug", `Loaded ${message.store}: ${message.pieces.length} ${message.pieces.length === 1 ? "piece" : "pieces"}`, {
|
|
703
|
+
channel: message.store === "commands" ? "commands" : "lifecycle",
|
|
704
|
+
detail: message.pieces.map((piece) => `${piece.name} ${relative(piece.path)}`)
|
|
705
|
+
});
|
|
706
|
+
return;
|
|
707
|
+
case "hmr":
|
|
708
|
+
this.#onHmr(message);
|
|
709
|
+
return;
|
|
710
|
+
case "commands":
|
|
711
|
+
this.#onCommands({
|
|
712
|
+
global: message.global,
|
|
713
|
+
guilds: message.guilds
|
|
714
|
+
});
|
|
715
|
+
return;
|
|
716
|
+
case "dispatch":
|
|
717
|
+
if (message.phase === "run") this.log("stars", "debug", `Processing ${message.route} with ${message.piece}`, { channel: "interactions" });
|
|
718
|
+
else if (message.phase === "success") this.log("stars", "trace", `${message.route} handled${formatMs(message.ms)}`, { channel: "interactions" });
|
|
719
|
+
else this.log("stars", "error", `${message.route} failed${formatMs(message.ms)}: ${message.error.message}`, {
|
|
720
|
+
channel: "interactions",
|
|
721
|
+
detail: message.error.stack
|
|
722
|
+
});
|
|
723
|
+
return;
|
|
724
|
+
case "request":
|
|
725
|
+
this.log("stars", message.status >= 500 ? "error" : message.status >= 400 ? "warn" : "trace", formatRequest(message), { channel: "http" });
|
|
726
|
+
return;
|
|
727
|
+
case "refreshed":
|
|
728
|
+
if (message.ok) {
|
|
729
|
+
this.#deployed = this.#commands;
|
|
730
|
+
const guilds = (message.guilds > 0 ? `, ${message.guilds} guild ${message.guilds === 1 ? "group" : "groups"}` : "") + (message.cleared > 0 ? `, cleared ${message.cleared} ${message.cleared === 1 ? "guild" : "guilds"}` : "");
|
|
731
|
+
this.log("stars", "success", `Deployed ${message.global} global ${message.global === 1 ? "command" : "commands"}${guilds}`, { channel: "commands" });
|
|
732
|
+
} else this.log("stars", "error", `Could not refresh the commands: ${message.message}`, {
|
|
733
|
+
channel: "commands",
|
|
734
|
+
detail: message.stack
|
|
735
|
+
});
|
|
736
|
+
return;
|
|
737
|
+
case "log":
|
|
738
|
+
this.log("app", toLogLevel(message.level), message.text, {
|
|
739
|
+
channel: message.channel,
|
|
740
|
+
detail: message.detail
|
|
741
|
+
});
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
#onHmr(message) {
|
|
746
|
+
const relative = (path) => displayPath(this.config.root, path);
|
|
747
|
+
if (message.event === "start") {
|
|
748
|
+
this.#hmrPaths = message.paths;
|
|
749
|
+
this.log("stars", "debug", `Hot reload on, watching ${message.paths.length} ${message.paths.length === 1 ? "path" : "paths"}`, {
|
|
750
|
+
channel: "hmr",
|
|
751
|
+
detail: message.paths.map(relative)
|
|
752
|
+
});
|
|
753
|
+
this.#emitStatus();
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (message.event === "stop") {
|
|
757
|
+
this.#hmrPaths = [];
|
|
758
|
+
this.log("stars", "debug", "Hot reload off", { channel: "hmr" });
|
|
759
|
+
this.#emitStatus();
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (message.event === "error") {
|
|
763
|
+
this.log("stars", "error", `Could not reload ${relative(message.path)}: ${message.message}`, {
|
|
764
|
+
channel: "hmr",
|
|
765
|
+
detail: message.stack
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (message.event === "unloaded") this.#pieces.delete(message.path);
|
|
770
|
+
else this.#pieces.add(message.path);
|
|
771
|
+
const verb = message.event === "reloaded" ? "Reloaded" : message.event === "loaded" ? "Loaded" : "Unloaded";
|
|
772
|
+
this.log("stars", "debug", `${verb} ${message.names.join(", ")} from ${relative(message.path)}`, { channel: "hmr" });
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Compares the commands the bot registers with the ones it reported before. The first report of a session is the
|
|
776
|
+
* baseline; a later one that differs is acted on as `dev.commands.refresh` says.
|
|
777
|
+
*/
|
|
778
|
+
#onCommands(snapshot) {
|
|
779
|
+
const previous = this.#commands;
|
|
780
|
+
this.#commands = snapshot;
|
|
781
|
+
if (previous === null) {
|
|
782
|
+
this.#deployed = snapshot;
|
|
783
|
+
const guilds = Object.keys(snapshot.guilds).length;
|
|
784
|
+
this.log("stars", "debug", `Loaded commands: ${snapshot.global.length} global, ${guilds} guild ${guilds === 1 ? "group" : "groups"}`, {
|
|
785
|
+
channel: "commands",
|
|
786
|
+
detail: snapshot.global.map((command) => describeCommand({
|
|
787
|
+
name: command.name,
|
|
788
|
+
type: command.type ?? 1,
|
|
789
|
+
guild: null
|
|
790
|
+
}))
|
|
791
|
+
});
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const changes = diffSnapshots(previous, snapshot);
|
|
795
|
+
if (changes.length === 0) return;
|
|
796
|
+
const detail = changes.map((change) => `${change.kind} ${describeCommand(change)}`);
|
|
797
|
+
const mode = this.config.dev.commands?.refresh ?? "prompt";
|
|
798
|
+
if (mode === "auto") {
|
|
799
|
+
this.log("stars", "info", "Commands updated", {
|
|
800
|
+
channel: "commands",
|
|
801
|
+
detail
|
|
802
|
+
});
|
|
803
|
+
this.refreshCommands();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (mode === "off" || !this.promptable) {
|
|
807
|
+
this.log("stars", "warn", "Commands updated, Discord still has the previous ones", {
|
|
808
|
+
channel: "commands",
|
|
809
|
+
detail: [...detail, mode === "off" ? "dev.commands.refresh is off" : "Set dev.commands.refresh to \"auto\" to redeploy without asking"]
|
|
810
|
+
});
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
this.#prompt = {
|
|
814
|
+
kind: "commands",
|
|
815
|
+
changes: mergeChanges(this.#prompt?.changes ?? [], changes)
|
|
816
|
+
};
|
|
817
|
+
this.log("stars", "info", "Commands updated", {
|
|
818
|
+
channel: "commands",
|
|
819
|
+
detail
|
|
820
|
+
});
|
|
821
|
+
this.#emitStatus();
|
|
822
|
+
}
|
|
505
823
|
/**
|
|
506
824
|
* Runs a hook behind the ones already running (see {@link DevService.#hookChain}), e.g. `dev:close` so shutdown
|
|
507
825
|
* waits for an unfinished `build:done`.
|
|
@@ -546,6 +864,7 @@ function createSupervisor(config) {
|
|
|
546
864
|
args: [
|
|
547
865
|
...envImportArgs(config),
|
|
548
866
|
...moduleImportArgs(config),
|
|
867
|
+
...bridgeImportArgs(config),
|
|
549
868
|
...config.dev.nodeArgs,
|
|
550
869
|
config.build.output,
|
|
551
870
|
...config.dev.args
|
|
@@ -558,9 +877,34 @@ function createSupervisor(config) {
|
|
|
558
877
|
NODE_ENV: "development",
|
|
559
878
|
FORCE_COLOR: process.env.NO_COLOR !== void 0 ? void 0 : process.env.FORCE_COLOR ?? (process.stdout.isTTY ? "1" : void 0)
|
|
560
879
|
},
|
|
561
|
-
killTimeout: config.dev.killTimeout
|
|
880
|
+
killTimeout: config.dev.killTimeout,
|
|
881
|
+
ipc: true
|
|
562
882
|
});
|
|
563
883
|
}
|
|
884
|
+
function formatMs(ms) {
|
|
885
|
+
return ms === null ? "" : ` in ${ms}ms`;
|
|
886
|
+
}
|
|
887
|
+
function formatRequest(message) {
|
|
888
|
+
return `${message.method} ${message.path} ${message.status} in ${message.ms}ms`;
|
|
889
|
+
}
|
|
890
|
+
const BRIDGE_LEVELS = /* @__PURE__ */ new Set([
|
|
891
|
+
"trace",
|
|
892
|
+
"debug",
|
|
893
|
+
"info",
|
|
894
|
+
"success",
|
|
895
|
+
"warn",
|
|
896
|
+
"error"
|
|
897
|
+
]);
|
|
898
|
+
function toLogLevel(level) {
|
|
899
|
+
return BRIDGE_LEVELS.has(level) ? level : "info";
|
|
900
|
+
}
|
|
901
|
+
/** The latest change of each command wins: `added` then `changed` is still one line of the prompt. */
|
|
902
|
+
function mergeChanges(previous, next) {
|
|
903
|
+
const key = (change) => `${change.guild ?? ""}:${change.type}:${change.name}`;
|
|
904
|
+
const merged = new Map(previous.map((change) => [key(change), change]));
|
|
905
|
+
for (const change of next) merged.set(key(change), change);
|
|
906
|
+
return [...merged.values()];
|
|
907
|
+
}
|
|
564
908
|
function describeReason(reason) {
|
|
565
909
|
switch (reason) {
|
|
566
910
|
case "initial": return "first build";
|
|
@@ -603,8 +947,8 @@ async function withResolvedLocalhost(url) {
|
|
|
603
947
|
//#region src/dev/log-file.ts
|
|
604
948
|
/**
|
|
605
949
|
* Mirrors a dev session's logs into a file, so a run can be read back after the terminal UI is gone (the TUI takes
|
|
606
|
-
* over the alternate screen and its scrollback disappears on exit).
|
|
607
|
-
*
|
|
950
|
+
* over the alternate screen and its scrollback disappears on exit). `dev.logFile` is a single file truncated on every
|
|
951
|
+
* run; `dev.logs.dir` adds one file per run (see {@link runLogFile}).
|
|
608
952
|
*/
|
|
609
953
|
var LogFileWriter = class {
|
|
610
954
|
file;
|
|
@@ -630,20 +974,57 @@ var LogFileWriter = class {
|
|
|
630
974
|
stream.end();
|
|
631
975
|
}
|
|
632
976
|
#write = (entry) => {
|
|
633
|
-
this.#stream?.write(`${
|
|
977
|
+
this.#stream?.write(`${formatLogLine(entry)}\n`);
|
|
634
978
|
};
|
|
635
979
|
};
|
|
636
|
-
|
|
637
|
-
|
|
980
|
+
/** One line per entry, then its detail lines indented under it. Never filtered: the file is the whole session. */
|
|
981
|
+
function formatLogLine(entry) {
|
|
982
|
+
return [`${new Date(entry.time).toISOString()} ${entry.level.padEnd(7)} ${entry.channel.padEnd(12)} ${stripVTControlCharacters(entry.text)}`, ...(entry.detail ?? []).map((line) => ` ${stripVTControlCharacters(line)}`)].join("\n");
|
|
983
|
+
}
|
|
984
|
+
const RUN_FILE = /^dev-.+\.log$/;
|
|
985
|
+
/**
|
|
986
|
+
* The file of this run inside `dev.logs.dir` (`dev-<timestamp>.log`), after deleting the oldest ones so the directory
|
|
987
|
+
* holds at most `keep` files with the new one. The names sort by time, so no file needs to be opened to order them.
|
|
988
|
+
*/
|
|
989
|
+
function runLogFile(directory, keep, now = /* @__PURE__ */ new Date()) {
|
|
990
|
+
let existing = [];
|
|
991
|
+
try {
|
|
992
|
+
existing = readdirSync(directory).filter((name) => RUN_FILE.test(name)).sort();
|
|
993
|
+
} catch {}
|
|
994
|
+
for (const name of existing.slice(0, Math.max(0, existing.length - Math.max(0, keep - 1)))) rmSync(join(directory, name), { force: true });
|
|
995
|
+
return join(directory, `dev-${now.toISOString().replaceAll(":", "-")}.log`);
|
|
638
996
|
}
|
|
639
997
|
|
|
640
998
|
//#endregion
|
|
641
999
|
//#region src/commands/dev.ts
|
|
1000
|
+
const LAYOUTS = [
|
|
1001
|
+
"auto",
|
|
1002
|
+
"dashboard",
|
|
1003
|
+
"panel"
|
|
1004
|
+
];
|
|
1005
|
+
const LEVELS = [
|
|
1006
|
+
"trace",
|
|
1007
|
+
"debug",
|
|
1008
|
+
"info",
|
|
1009
|
+
"warn",
|
|
1010
|
+
"error"
|
|
1011
|
+
];
|
|
642
1012
|
async function runDev(options) {
|
|
643
1013
|
if (options.theme !== void 0 && !isThemeSetting(options.theme)) throw cliDiagnostics.INVALID_THEME({
|
|
644
1014
|
theme: options.theme,
|
|
645
1015
|
themes: THEME_SETTINGS.join(", ")
|
|
646
1016
|
});
|
|
1017
|
+
const layout = options.layout;
|
|
1018
|
+
if (layout !== void 0 && !LAYOUTS.includes(layout)) throw cliDiagnostics.INVALID_OPTION({
|
|
1019
|
+
option: "--layout",
|
|
1020
|
+
value: options.layout,
|
|
1021
|
+
allowed: LAYOUTS.join(", ")
|
|
1022
|
+
});
|
|
1023
|
+
if (options.level !== void 0 && !isLogLevel(options.level)) throw cliDiagnostics.INVALID_OPTION({
|
|
1024
|
+
option: "--level",
|
|
1025
|
+
value: options.level,
|
|
1026
|
+
allowed: LEVELS.join(", ")
|
|
1027
|
+
});
|
|
647
1028
|
process.env.NODE_ENV = "development";
|
|
648
1029
|
const project = await loadProject({
|
|
649
1030
|
cwd: resolveCwd(options),
|
|
@@ -668,23 +1049,32 @@ async function runDev(options) {
|
|
|
668
1049
|
hooks
|
|
669
1050
|
});
|
|
670
1051
|
await reportWarnings(config, (text) => service.log("stars", "warn", text), { production: false });
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
const
|
|
1052
|
+
const logFiles = [...config.dev.logFile ? [config.dev.logFile] : [], ...config.dev.logs?.dir ? [runLogFile(config.dev.logs.dir, config.dev.logs.keep)] : []].map((file) => new LogFileWriter(file, service.logs));
|
|
1053
|
+
for (const file of logFiles) file.open();
|
|
1054
|
+
const filter = initialLogView(config, {
|
|
1055
|
+
channels: options.channel,
|
|
1056
|
+
level: options.level
|
|
1057
|
+
});
|
|
1058
|
+
const renderer = mode === "tui" ? (await import("./tui-nldGSD4c.js")).createTuiRenderer(service, {
|
|
674
1059
|
color,
|
|
675
1060
|
theme,
|
|
1061
|
+
layout,
|
|
1062
|
+
filter,
|
|
676
1063
|
reducedMotion: prefersReducedMotion(),
|
|
677
1064
|
onThemeSave: (setting) => {
|
|
678
1065
|
if (!saveTheme(setting)) service.log("stars", "warn", "Could not save the theme preference.");
|
|
679
1066
|
}
|
|
680
|
-
}) : (await import("./plain-
|
|
1067
|
+
}) : (await import("./plain-DG7cy0Kk.js")).createPlainRenderer(service, {
|
|
1068
|
+
color,
|
|
1069
|
+
filter
|
|
1070
|
+
});
|
|
681
1071
|
let exiting = null;
|
|
682
1072
|
const shutdown = (code) => {
|
|
683
1073
|
exiting ??= (async () => {
|
|
684
1074
|
renderer.stop();
|
|
685
1075
|
await service.runHook("dev:close", config);
|
|
686
1076
|
await service.stop();
|
|
687
|
-
|
|
1077
|
+
for (const file of logFiles) file.close();
|
|
688
1078
|
process.exit(code);
|
|
689
1079
|
})();
|
|
690
1080
|
return exiting;
|
|
@@ -746,18 +1136,34 @@ var dev_default = defineCommand({
|
|
|
746
1136
|
theme: {
|
|
747
1137
|
type: "string",
|
|
748
1138
|
description: `Colour theme of the interactive UI: ${THEME_SETTINGS.join(", ")} (or STARS_THEME; press T in the UI to pick one)`
|
|
1139
|
+
},
|
|
1140
|
+
layout: {
|
|
1141
|
+
type: "string",
|
|
1142
|
+
description: `Layout of the interactive UI: ${LAYOUTS.join(", ")} (defaults to dev.layout; press v in the UI to switch)`
|
|
1143
|
+
},
|
|
1144
|
+
channel: {
|
|
1145
|
+
type: "string",
|
|
1146
|
+
description: "Only show these log channels at start (repeatable, or comma-separated; defaults to dev.logs.channels)"
|
|
1147
|
+
},
|
|
1148
|
+
level: {
|
|
1149
|
+
type: "string",
|
|
1150
|
+
description: `Only show this log level and the more severe ones at start: ${LEVELS.join(", ")} (defaults to dev.logs.levels)`
|
|
749
1151
|
}
|
|
750
1152
|
},
|
|
751
|
-
async run({ args }) {
|
|
1153
|
+
async run({ args, rawArgs }) {
|
|
1154
|
+
const channel = collectFlag(rawArgs, "channel");
|
|
752
1155
|
await runDev({
|
|
753
1156
|
config: args.config,
|
|
754
1157
|
cwd: args.cwd,
|
|
755
1158
|
tui: args.tui ? void 0 : false,
|
|
756
|
-
theme: args.theme
|
|
1159
|
+
theme: args.theme,
|
|
1160
|
+
layout: args.layout,
|
|
1161
|
+
channel: channel.length > 0 ? channel : void 0,
|
|
1162
|
+
level: args.level
|
|
757
1163
|
});
|
|
758
1164
|
}
|
|
759
1165
|
});
|
|
760
1166
|
|
|
761
1167
|
//#endregion
|
|
762
1168
|
export { dev_default as default };
|
|
763
|
-
//# sourceMappingURL=dev-
|
|
1169
|
+
//# sourceMappingURL=dev-oPPVR7d_.js.map
|