@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.
- package/README.md +203 -42
- package/dist/{_shared-B-hRZDRS.js → _shared-BIQK0Bhm.js} +12 -5
- 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-CPnSLYME.js → build-DJcc95Yq.js} +5 -5
- package/dist/{build-CPnSLYME.js.map → build-DJcc95Yq.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/{codegen-BqpLGLLs.js → codegen-aJSyPz-J.js} +4 -4
- package/dist/{codegen-BqpLGLLs.js.map → codegen-aJSyPz-J.js.map} +1 -1
- 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-ExM8HILu.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-4wpEQC7l.js → dev-oPPVR7d_.js} +432 -45
- package/dist/dev-oPPVR7d_.js.map +1 -0
- package/dist/{diagnostics-CZBVxlpj.js → diagnostics-BysbNnPD.js} +38 -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-Crt4hu0b.js → errors-COFWKKKw.js} +5 -2
- package/dist/errors-COFWKKKw.js.map +1 -0
- package/dist/hooks-Cy6F2bhH.js +332 -0
- package/dist/hooks-Cy6F2bhH.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{info-CdiQajgn.js → info-CKJVMWIT.js} +16 -5
- package/dist/info-CKJVMWIT.js.map +1 -0
- package/dist/{locales-C7X8VNmK.js → locales-BJAP4HYf.js} +5 -5
- package/dist/{locales-C7X8VNmK.js.map → locales-BJAP4HYf.js.map} +1 -1
- package/dist/log-view-B7uJEnMR.js +342 -0
- package/dist/log-view-B7uJEnMR.js.map +1 -0
- package/dist/{nitro-sP2F3SQu.js → nitro-B8u1iVgf.js} +2 -2
- package/dist/{nitro-sP2F3SQu.js.map → nitro-B8u1iVgf.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-B4ysyG6H.js → prepare-BM93i3Rm.js} +9 -5
- package/dist/prepare-BM93i3Rm.js.map +1 -0
- package/dist/{project-MD4dIo2X.js → project-zlQStwsf.js} +2 -2
- package/dist/{project-MD4dIo2X.js.map → project-zlQStwsf.js.map} +1 -1
- package/dist/{run-Ddwrxe1O.js → run-DMM2YlP3.js} +3 -13
- 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-DO7D_uCl.js → tsc-puVAcwmo.js} +24 -5
- package/dist/tsc-puVAcwmo.js.map +1 -0
- package/dist/{tsdown-CD68G8_U.js → tsdown-Dwt3JhPw.js} +2 -2
- package/dist/{tsdown-CD68G8_U.js.map → tsdown-Dwt3JhPw.js.map} +1 -1
- package/dist/{tui-BRddrLQP.js → tui-nldGSD4c.js} +913 -141
- package/dist/tui-nldGSD4c.js.map +1 -0
- package/dist/{vite-CLyB6wl7.js → vite-YZUtv2Hk.js} +2 -2
- package/dist/{vite-CLyB6wl7.js.map → vite-YZUtv2Hk.js.map} +1 -1
- package/package.json +5 -4
- package/dist/_shared-B-hRZDRS.js.map +0 -1
- package/dist/commands-ExM8HILu.js.map +0 -1
- package/dist/dev-4wpEQC7l.js.map +0 -1
- package/dist/diagnostics-CZBVxlpj.js.map +0 -1
- package/dist/errors-Crt4hu0b.js.map +0 -1
- package/dist/hooks-H9kjLCMk.js +0 -148
- package/dist/hooks-H9kjLCMk.js.map +0 -1
- package/dist/info-CdiQajgn.js.map +0 -1
- package/dist/log-buffer-CY82aqnb.js +0 -72
- package/dist/log-buffer-CY82aqnb.js.map +0 -1
- package/dist/plain-B6z85uQP.js.map +0 -1
- package/dist/prepare-B4ysyG6H.js.map +0 -1
- package/dist/run-Ddwrxe1O.js.map +0 -1
- package/dist/theme-3Fc_LI-2.js.map +0 -1
- package/dist/tsc-DO7D_uCl.js.map +0 -1
- package/dist/tsc-YnAWKn3n.js +0 -3
- package/dist/tui-BRddrLQP.js.map +0 -1
- package/dist/tunnel-DT1FH6oA.js +0 -175
- package/dist/tunnel-DT1FH6oA.js.map +0 -1
|
@@ -1,39 +1,94 @@
|
|
|
1
1
|
import { i as shouldUseColor, n as prefersReducedMotion, r as resolveOutputMode } from "./output-mode-Bp7Da8vJ.js";
|
|
2
|
-
import { i as renderCrashReport, t as ExitCode } from "./errors-
|
|
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 {
|
|
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 {
|
|
9
|
-
import { n as reportWarnings, t as prepareProject } from "./_shared-
|
|
10
|
-
import { t as
|
|
11
|
-
import { n as
|
|
2
|
+
import { i as renderCrashReport, t as ExitCode } from "./errors-COFWKKKw.js";
|
|
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";
|
|
15
|
-
import { pathToFileURL } from "node:url";
|
|
14
|
+
import { createWriteStream, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
15
|
+
import { basename, dirname, extname, join, sep } from "node:path";
|
|
16
16
|
import { displayPath } from "@wolfstar/schema";
|
|
17
17
|
import { EventEmitter } from "node:events";
|
|
18
|
-
import { envModuleSource } from "@wolfstar/vite-server/internal";
|
|
19
18
|
import { spawn } from "node:child_process";
|
|
19
|
+
import { stripVTControlCharacters } from "node:util";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
20
21
|
import { lookup } from "node:dns/promises";
|
|
21
22
|
|
|
22
|
-
//#region src/
|
|
23
|
-
|
|
24
|
-
const UNTRANSFORMED_TOOLS = /* @__PURE__ */ new Set(["none", "tsc"]);
|
|
23
|
+
//#region src/dev/changed-files.ts
|
|
24
|
+
const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
25
25
|
/**
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
* specifiers, so `@wolfstar/env-utilities` is resolved from the project first. When it cannot be resolved nothing is
|
|
29
|
-
* preloaded, and the bot's own import of it reports the missing install.
|
|
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.
|
|
30
28
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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}`);
|
|
37
92
|
}
|
|
38
93
|
|
|
39
94
|
//#endregion
|
|
@@ -252,6 +307,8 @@ var DevService = class extends EventEmitter {
|
|
|
252
307
|
typechecker;
|
|
253
308
|
tunnel;
|
|
254
309
|
locales;
|
|
310
|
+
/** Whether something can answer a {@link DevPrompt}: the interactive UI sets it, the plain renderer cannot. */
|
|
311
|
+
promptable = false;
|
|
255
312
|
#hooks;
|
|
256
313
|
/** Runs hooks one at a time, so an async `build:done` settles before the next build's hooks or a restart. */
|
|
257
314
|
#hookChain = Promise.resolve();
|
|
@@ -272,6 +329,23 @@ var DevService = class extends EventEmitter {
|
|
|
272
329
|
startedAt: Date.now(),
|
|
273
330
|
readyMs: null
|
|
274
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;
|
|
275
349
|
constructor(config, options) {
|
|
276
350
|
super();
|
|
277
351
|
this.config = config;
|
|
@@ -312,6 +386,15 @@ var DevService = class extends EventEmitter {
|
|
|
312
386
|
});
|
|
313
387
|
this.supervisor.on("stdout", (line) => this.log("app", classifyAppLine(line, "info"), line));
|
|
314
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
|
+
});
|
|
315
398
|
this.supervisor.on("error", (error) => this.log("stars", "error", `Failed to start the bot: ${error.message}`));
|
|
316
399
|
this.supervisor.on("exit", (exit) => this.#onExit(exit));
|
|
317
400
|
this.typechecker.on("log", (level, text) => this.log("tsc", level, text));
|
|
@@ -344,14 +427,20 @@ var DevService = class extends EventEmitter {
|
|
|
344
427
|
typecheck: this.typechecker.state,
|
|
345
428
|
typeErrors: this.typechecker.errors,
|
|
346
429
|
tunnel: this.tunnel.state,
|
|
347
|
-
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
|
|
348
436
|
};
|
|
349
437
|
}
|
|
350
|
-
log(source, level, text) {
|
|
438
|
+
log(source, level, text, extra = {}) {
|
|
351
439
|
this.logs.push({
|
|
352
440
|
source,
|
|
353
441
|
level,
|
|
354
|
-
text
|
|
442
|
+
text,
|
|
443
|
+
...extra
|
|
355
444
|
});
|
|
356
445
|
}
|
|
357
446
|
/**
|
|
@@ -394,10 +483,51 @@ var DevService = class extends EventEmitter {
|
|
|
394
483
|
message: "starting the bot"
|
|
395
484
|
};
|
|
396
485
|
this.#health = "unknown";
|
|
486
|
+
this.#paused = false;
|
|
397
487
|
this.supervisor.start();
|
|
398
488
|
this.#emitStatus();
|
|
399
489
|
});
|
|
400
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
|
+
}
|
|
401
531
|
/** Opens or closes the public tunnel (used by the `t` key). */
|
|
402
532
|
toggleTunnel() {
|
|
403
533
|
return this.tunnel.toggle();
|
|
@@ -434,6 +564,7 @@ var DevService = class extends EventEmitter {
|
|
|
434
564
|
this.logs.clear();
|
|
435
565
|
}
|
|
436
566
|
#onBuildSuccess(outcome) {
|
|
567
|
+
const hot = this.#hotReloadable();
|
|
437
568
|
try {
|
|
438
569
|
this.locales.copy();
|
|
439
570
|
} catch (error) {
|
|
@@ -455,8 +586,35 @@ var DevService = class extends EventEmitter {
|
|
|
455
586
|
if (this.#stopped) return;
|
|
456
587
|
this.typechecker.check();
|
|
457
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
|
+
}
|
|
458
596
|
this.#scheduleRestart(this.supervisor.state === "idle" ? "initial" : "build");
|
|
459
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
|
+
}
|
|
460
618
|
#onBuildFailure(outcome) {
|
|
461
619
|
this.#lastBuild = outcome;
|
|
462
620
|
this.#queueHook("build:done", outcome, this.config);
|
|
@@ -467,6 +625,10 @@ var DevService = class extends EventEmitter {
|
|
|
467
625
|
#onExit(exit) {
|
|
468
626
|
this.#lastExit = exit;
|
|
469
627
|
this.#health = "unknown";
|
|
628
|
+
this.#ready = false;
|
|
629
|
+
this.#port = null;
|
|
630
|
+
this.#hmrPaths = [];
|
|
631
|
+
this.#pieces.clear();
|
|
470
632
|
if (!exit.requested) {
|
|
471
633
|
const how = exit.signal ? `signal ${exit.signal}` : `code ${exit.code}`;
|
|
472
634
|
this.log("stars", exit.code === 0 ? "warn" : "error", `The bot exited with ${how}, waiting for changes (press r to restart)`);
|
|
@@ -474,6 +636,7 @@ var DevService = class extends EventEmitter {
|
|
|
474
636
|
this.#emitStatus();
|
|
475
637
|
}
|
|
476
638
|
#scheduleRestart(reason) {
|
|
639
|
+
if (this.#paused) return;
|
|
477
640
|
this.#pendingReason = reason;
|
|
478
641
|
this.#clearRestartTimer();
|
|
479
642
|
this.#restartTimer = setTimeout(() => {
|
|
@@ -522,6 +685,141 @@ var DevService = class extends EventEmitter {
|
|
|
522
685
|
readyMs: this.#progress.readyMs ?? Date.now() - this.#progress.startedAt
|
|
523
686
|
};
|
|
524
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
|
+
}
|
|
525
823
|
/**
|
|
526
824
|
* Runs a hook behind the ones already running (see {@link DevService.#hookChain}), e.g. `dev:close` so shutdown
|
|
527
825
|
* waits for an unfinished `build:done`.
|
|
@@ -565,6 +863,8 @@ function createSupervisor(config) {
|
|
|
565
863
|
command: process.execPath,
|
|
566
864
|
args: [
|
|
567
865
|
...envImportArgs(config),
|
|
866
|
+
...moduleImportArgs(config),
|
|
867
|
+
...bridgeImportArgs(config),
|
|
568
868
|
...config.dev.nodeArgs,
|
|
569
869
|
config.build.output,
|
|
570
870
|
...config.dev.args
|
|
@@ -577,9 +877,34 @@ function createSupervisor(config) {
|
|
|
577
877
|
NODE_ENV: "development",
|
|
578
878
|
FORCE_COLOR: process.env.NO_COLOR !== void 0 ? void 0 : process.env.FORCE_COLOR ?? (process.stdout.isTTY ? "1" : void 0)
|
|
579
879
|
},
|
|
580
|
-
killTimeout: config.dev.killTimeout
|
|
880
|
+
killTimeout: config.dev.killTimeout,
|
|
881
|
+
ipc: true
|
|
581
882
|
});
|
|
582
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
|
+
}
|
|
583
908
|
function describeReason(reason) {
|
|
584
909
|
switch (reason) {
|
|
585
910
|
case "initial": return "first build";
|
|
@@ -622,8 +947,8 @@ async function withResolvedLocalhost(url) {
|
|
|
622
947
|
//#region src/dev/log-file.ts
|
|
623
948
|
/**
|
|
624
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
|
|
625
|
-
* over the alternate screen and its scrollback disappears on exit).
|
|
626
|
-
*
|
|
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}).
|
|
627
952
|
*/
|
|
628
953
|
var LogFileWriter = class {
|
|
629
954
|
file;
|
|
@@ -649,20 +974,57 @@ var LogFileWriter = class {
|
|
|
649
974
|
stream.end();
|
|
650
975
|
}
|
|
651
976
|
#write = (entry) => {
|
|
652
|
-
this.#stream?.write(`${
|
|
977
|
+
this.#stream?.write(`${formatLogLine(entry)}\n`);
|
|
653
978
|
};
|
|
654
979
|
};
|
|
655
|
-
|
|
656
|
-
|
|
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`);
|
|
657
996
|
}
|
|
658
997
|
|
|
659
998
|
//#endregion
|
|
660
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
|
+
];
|
|
661
1012
|
async function runDev(options) {
|
|
662
1013
|
if (options.theme !== void 0 && !isThemeSetting(options.theme)) throw cliDiagnostics.INVALID_THEME({
|
|
663
1014
|
theme: options.theme,
|
|
664
1015
|
themes: THEME_SETTINGS.join(", ")
|
|
665
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
|
+
});
|
|
666
1028
|
process.env.NODE_ENV = "development";
|
|
667
1029
|
const project = await loadProject({
|
|
668
1030
|
cwd: resolveCwd(options),
|
|
@@ -686,24 +1048,33 @@ async function runDev(options) {
|
|
|
686
1048
|
builder,
|
|
687
1049
|
hooks
|
|
688
1050
|
});
|
|
689
|
-
await reportWarnings(config, (text) => service.log("stars", "warn", text));
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
const
|
|
1051
|
+
await reportWarnings(config, (text) => service.log("stars", "warn", text), { production: false });
|
|
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, {
|
|
693
1059
|
color,
|
|
694
1060
|
theme,
|
|
1061
|
+
layout,
|
|
1062
|
+
filter,
|
|
695
1063
|
reducedMotion: prefersReducedMotion(),
|
|
696
1064
|
onThemeSave: (setting) => {
|
|
697
1065
|
if (!saveTheme(setting)) service.log("stars", "warn", "Could not save the theme preference.");
|
|
698
1066
|
}
|
|
699
|
-
}) : (await import("./plain-
|
|
1067
|
+
}) : (await import("./plain-DG7cy0Kk.js")).createPlainRenderer(service, {
|
|
1068
|
+
color,
|
|
1069
|
+
filter
|
|
1070
|
+
});
|
|
700
1071
|
let exiting = null;
|
|
701
1072
|
const shutdown = (code) => {
|
|
702
1073
|
exiting ??= (async () => {
|
|
703
1074
|
renderer.stop();
|
|
704
1075
|
await service.runHook("dev:close", config);
|
|
705
1076
|
await service.stop();
|
|
706
|
-
|
|
1077
|
+
for (const file of logFiles) file.close();
|
|
707
1078
|
process.exit(code);
|
|
708
1079
|
})();
|
|
709
1080
|
return exiting;
|
|
@@ -765,18 +1136,34 @@ var dev_default = defineCommand({
|
|
|
765
1136
|
theme: {
|
|
766
1137
|
type: "string",
|
|
767
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)`
|
|
768
1151
|
}
|
|
769
1152
|
},
|
|
770
|
-
async run({ args }) {
|
|
1153
|
+
async run({ args, rawArgs }) {
|
|
1154
|
+
const channel = collectFlag(rawArgs, "channel");
|
|
771
1155
|
await runDev({
|
|
772
1156
|
config: args.config,
|
|
773
1157
|
cwd: args.cwd,
|
|
774
1158
|
tui: args.tui ? void 0 : false,
|
|
775
|
-
theme: args.theme
|
|
1159
|
+
theme: args.theme,
|
|
1160
|
+
layout: args.layout,
|
|
1161
|
+
channel: channel.length > 0 ? channel : void 0,
|
|
1162
|
+
level: args.level
|
|
776
1163
|
});
|
|
777
1164
|
}
|
|
778
1165
|
});
|
|
779
1166
|
|
|
780
1167
|
//#endregion
|
|
781
1168
|
export { dev_default as default };
|
|
782
|
-
//# sourceMappingURL=dev-
|
|
1169
|
+
//# sourceMappingURL=dev-oPPVR7d_.js.map
|