seedcord 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +921 -383
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-pMIoZiL7.mjs → src-C9EuHVMn.mjs} +2 -2
- package/dist/src-C9EuHVMn.mjs.map +1 -0
- package/package.json +24 -23
- package/dist/src-pMIoZiL7.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { n as SEEDCORD_CONFIG_FILENAMES, t as version } from "./src-
|
|
1
|
+
import { n as SEEDCORD_CONFIG_FILENAMES, t as version } from "./src-C9EuHVMn.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Command } from "@commander-js/extra-typings";
|
|
4
|
-
import { LEVEL_COLOR, Logger, LoggerChannelRegistry } from "@seedcord/logger";
|
|
4
|
+
import { LEVEL_COLOR, Logger, LoggerChannelRegistry, paint } from "@seedcord/logger";
|
|
5
5
|
import { formatBody, installNodeDefaults } from "@seedcord/logger/node";
|
|
6
6
|
import { SeedcordErrorCode, isSeedcordError } from "@seedcord/errors";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { SeedcordError, validateDiscordToken } from "@seedcord/errors/internal";
|
|
9
|
-
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
|
+
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { pathToFileURL } from "node:url";
|
|
11
11
|
import { createJiti } from "jiti";
|
|
12
12
|
import { tsImport } from "tsx/esm/api";
|
|
@@ -14,22 +14,25 @@ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
import { SeedcordBrand } from "@seedcord/types/internal";
|
|
16
16
|
import { isTsOrJsFile } from "@seedcord/utils/node";
|
|
17
|
-
import { ApplicationCommandOptionType, ApplicationCommandType, Routes } from "discord-api-types/v10";
|
|
17
|
+
import { ApplicationCommandOptionType, ApplicationCommandType, RESTJSONErrorCodes, Routes } from "discord-api-types/v10";
|
|
18
18
|
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
19
19
|
import { Envapter } from "envapt";
|
|
20
|
-
import { REST } from "@discordjs/rest";
|
|
20
|
+
import { DiscordAPIError, REST } from "@discordjs/rest";
|
|
21
21
|
import chalk from "chalk";
|
|
22
22
|
import { autocompleteMultiselect, cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, spinner } from "@clack/prompts";
|
|
23
23
|
import { createInterface } from "node:readline";
|
|
24
24
|
import { Box, Text, measureElement, render, useAnimation, useInput, useWindowSize } from "ink";
|
|
25
25
|
import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
26
26
|
import { TypedEventEmitter } from "@seedcord/event-emitter";
|
|
27
|
+
import wrapAnsi from "wrap-ansi";
|
|
27
28
|
import Spinner from "ink-spinner";
|
|
28
29
|
import { assertNever } from "@seedcord/utils";
|
|
30
|
+
import { settleWithin } from "@seedcord/core/node/internal";
|
|
29
31
|
import { createServer, createServerModuleRunner, defineConfig, mergeConfig } from "vite";
|
|
30
32
|
import { EvaluatedModules } from "vite/module-runner";
|
|
31
33
|
import { wrapHot } from "@seedcord/core/internal";
|
|
32
34
|
import { minimatch } from "minimatch";
|
|
35
|
+
import { createServer as createServer$1 } from "node:net";
|
|
33
36
|
|
|
34
37
|
//#region src/core/BaseCommand.ts
|
|
35
38
|
var BaseCommand = class {
|
|
@@ -83,11 +86,24 @@ function validateHmr(value) {
|
|
|
83
86
|
if (value.restart !== void 0 && !isStringArray(value.restart)) throw new SeedcordError(SeedcordErrorCode.CliConfigInvalidHmrRestart);
|
|
84
87
|
if (value.rollback !== void 0 && typeof value.rollback !== "boolean") throw new SeedcordError(SeedcordErrorCode.CliConfigInvalidHmrRollback);
|
|
85
88
|
}
|
|
89
|
+
function validateTunnel(value) {
|
|
90
|
+
if (value === void 0 || typeof value === "boolean") return;
|
|
91
|
+
if (typeof value !== "string" || URL.parse(value)?.protocol !== "https:") throw new SeedcordError(SeedcordErrorCode.CliConfigInvalidTunnel);
|
|
92
|
+
}
|
|
93
|
+
function resolveTunnel(value) {
|
|
94
|
+
if (value === false) return { mode: "off" };
|
|
95
|
+
if (typeof value === "string") return {
|
|
96
|
+
mode: "url",
|
|
97
|
+
url: value
|
|
98
|
+
};
|
|
99
|
+
return { mode: "quick" };
|
|
100
|
+
}
|
|
86
101
|
function validateConfig(raw) {
|
|
87
102
|
if (!isPlainObject(raw)) throw new SeedcordError(SeedcordErrorCode.CliConfigInvalidExport);
|
|
88
103
|
if (typeof raw.instance !== "string" || raw.instance.length === 0) throw new SeedcordError(SeedcordErrorCode.CliConfigMissingInstance);
|
|
89
104
|
if (typeof raw.entry !== "string" || raw.entry.length === 0) throw new SeedcordError(SeedcordErrorCode.CliConfigMissingEntry);
|
|
90
105
|
if (!isOptionalString(raw.root)) throw new SeedcordError(SeedcordErrorCode.CliConfigInvalidRoot);
|
|
106
|
+
validateTunnel(raw.tunnel);
|
|
91
107
|
validateBuild(raw.build);
|
|
92
108
|
validateHmr(raw.hmr);
|
|
93
109
|
}
|
|
@@ -109,14 +125,14 @@ var ConfigLoader = class {
|
|
|
109
125
|
this.assertEntryWithinRoot(root, entry);
|
|
110
126
|
const build = this.resolveBuildOptions(configDir, config.build);
|
|
111
127
|
const tsconfig = config.hmr?.tsconfig ? resolve(root, config.hmr.tsconfig) : void 0;
|
|
112
|
-
this.logger.
|
|
113
|
-
this.logger.
|
|
114
|
-
this.logger.
|
|
115
|
-
this.logger.
|
|
116
|
-
this.logger.
|
|
117
|
-
if (build.tsconfig) this.logger.
|
|
118
|
-
if (tsconfig) this.logger.
|
|
119
|
-
this.logger.
|
|
128
|
+
this.logger.debug(`Loaded configuration from ${configPath}`);
|
|
129
|
+
this.logger.trace(`Resolved root: ${root}`);
|
|
130
|
+
this.logger.trace(`Resolved instance: ${instance}`);
|
|
131
|
+
this.logger.trace(`Resolved entry: ${entry}`);
|
|
132
|
+
this.logger.trace(`Resolved build outDir: ${build.outDir}`);
|
|
133
|
+
if (build.tsconfig) this.logger.trace(`Resolved build tsconfig: ${build.tsconfig}`);
|
|
134
|
+
if (tsconfig) this.logger.trace(`Resolved dev tsconfig: ${tsconfig}`);
|
|
135
|
+
this.logger.trace(`Resolved bootstrap: ${build.bootstrap}`);
|
|
120
136
|
return {
|
|
121
137
|
instance,
|
|
122
138
|
root,
|
|
@@ -124,6 +140,7 @@ var ConfigLoader = class {
|
|
|
124
140
|
entry,
|
|
125
141
|
build,
|
|
126
142
|
tsconfig,
|
|
143
|
+
tunnel: resolveTunnel(config.tunnel),
|
|
127
144
|
hmr: config.hmr
|
|
128
145
|
};
|
|
129
146
|
}
|
|
@@ -167,7 +184,7 @@ var ConfigLocator = class {
|
|
|
167
184
|
for (const candidate of SEEDCORD_CONFIG_FILENAMES) {
|
|
168
185
|
const fullPath = join(normalizedBase, candidate);
|
|
169
186
|
if (existsSync(fullPath)) {
|
|
170
|
-
this.logger.
|
|
187
|
+
this.logger.trace(`Found config at ${fullPath}`);
|
|
171
188
|
return fullPath;
|
|
172
189
|
}
|
|
173
190
|
}
|
|
@@ -177,7 +194,7 @@ var ConfigLocator = class {
|
|
|
177
194
|
|
|
178
195
|
//#endregion
|
|
179
196
|
//#region src/core/modules/RuntimeModuleLoader.ts
|
|
180
|
-
const TS_EXTENSIONS = new Set([
|
|
197
|
+
const TS_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
181
198
|
".ts",
|
|
182
199
|
".tsx",
|
|
183
200
|
".mts",
|
|
@@ -438,7 +455,11 @@ var BuildRunner = class BuildRunner {
|
|
|
438
455
|
}
|
|
439
456
|
static create(logger) {
|
|
440
457
|
const moduleLoader = new RuntimeModuleLoader();
|
|
441
|
-
|
|
458
|
+
const locator = new ConfigLocator(logger);
|
|
459
|
+
const configLoader = new ConfigLoader(moduleLoader, logger);
|
|
460
|
+
const builder = new TypeScriptProjectBuilder(logger);
|
|
461
|
+
const bootstrapWriter = new BootstrapWriter(logger);
|
|
462
|
+
return new BuildRunner(locator, configLoader, builder, bootstrapWriter, logger);
|
|
442
463
|
}
|
|
443
464
|
async run() {
|
|
444
465
|
const config = await this.loadConfig();
|
|
@@ -521,9 +542,7 @@ var AugmentationBuilder = class {
|
|
|
521
542
|
this.collectContextMenu("message", json, command.sourceFile, sourceByMessageName);
|
|
522
543
|
break;
|
|
523
544
|
case void 0:
|
|
524
|
-
case ApplicationCommandType.ChatInput:
|
|
525
|
-
this.collectSlash(json, command.sourceFile, slash, sourceByRoute);
|
|
526
|
-
break;
|
|
545
|
+
case ApplicationCommandType.ChatInput: this.collectSlash(json, command.sourceFile, slash, sourceByRoute);
|
|
527
546
|
}
|
|
528
547
|
}
|
|
529
548
|
const userContextMenus = [...sourceByUserName.keys()];
|
|
@@ -588,7 +607,7 @@ var AugmentationBuilder = class {
|
|
|
588
607
|
//#endregion
|
|
589
608
|
//#region src/commands/codegen/renderAugmentation.ts
|
|
590
609
|
const BANNER = `// Generated by \`seedcord codegen\`. Do not edit by hand.
|
|
591
|
-
// Run \`seedcord codegen\` after changing your commands or emoji config.`;
|
|
610
|
+
// Run \`seedcord codegen\` after changing your commands, plugins, or emoji config.`;
|
|
592
611
|
const BOT_BINDING = "Bot";
|
|
593
612
|
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
594
613
|
const CONTROL_CHAR = 32;
|
|
@@ -693,7 +712,10 @@ var CodegenRunner = class CodegenRunner {
|
|
|
693
712
|
}
|
|
694
713
|
static create(logger) {
|
|
695
714
|
const moduleLoader = new RuntimeModuleLoader();
|
|
696
|
-
|
|
715
|
+
const locator = new ConfigLocator(logger);
|
|
716
|
+
const configLoader = new ConfigLoader(moduleLoader, logger);
|
|
717
|
+
const generator = new AugmentationBuilder(logger);
|
|
718
|
+
return new CodegenRunner(locator, configLoader, moduleLoader, generator, logger);
|
|
697
719
|
}
|
|
698
720
|
async run(check) {
|
|
699
721
|
const config = await this.loadConfig();
|
|
@@ -751,8 +773,9 @@ var CodegenRunner = class CodegenRunner {
|
|
|
751
773
|
}
|
|
752
774
|
}
|
|
753
775
|
async resolveInstance(config) {
|
|
754
|
-
this.logger.
|
|
755
|
-
const
|
|
776
|
+
this.logger.debug("Loading instance to resolve the commands directory");
|
|
777
|
+
const module = await this.moduleLoader.importModule(config.instance);
|
|
778
|
+
const instance = resolveDefaultExport(module);
|
|
756
779
|
if (!this.isSeedcordInstance(instance)) throw new SeedcordError(SeedcordErrorCode.CliInstanceInvalid);
|
|
757
780
|
const commandsPath = instance.config.bot.commands.path;
|
|
758
781
|
return {
|
|
@@ -1018,7 +1041,7 @@ async function confirm$1(opts) {
|
|
|
1018
1041
|
}
|
|
1019
1042
|
|
|
1020
1043
|
//#endregion
|
|
1021
|
-
//#region \0@oxc-project+runtime@0.
|
|
1044
|
+
//#region \0@oxc-project+runtime@0.142.0/helpers/esm/usingCtx.js
|
|
1022
1045
|
function _usingCtx() {
|
|
1023
1046
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
1024
1047
|
var n = Error();
|
|
@@ -1148,7 +1171,8 @@ var InteractivePresenter = class {
|
|
|
1148
1171
|
}
|
|
1149
1172
|
}
|
|
1150
1173
|
preview(scan) {
|
|
1151
|
-
|
|
1174
|
+
const body = groupByGuild(scan.flagged).map((group) => `${chalk.bold(group.guildName)}\n${group.commands.map((c) => ` ${c.name}`).join("\n")}`).join("\n\n");
|
|
1175
|
+
note(body, `Will delete ${plural(scan.flagged.length, "guild command")} (nothing deleted yet)`);
|
|
1152
1176
|
if (scan.skipped.length > 0) log.warn(skippedSummary(scan.skipped));
|
|
1153
1177
|
}
|
|
1154
1178
|
largeBotGuard(guildCount) {
|
|
@@ -1421,6 +1445,20 @@ var CommandsCommand = class extends BaseCommand {
|
|
|
1421
1445
|
}
|
|
1422
1446
|
};
|
|
1423
1447
|
|
|
1448
|
+
//#endregion
|
|
1449
|
+
//#region src/ui/components/primitives/Rule.tsx
|
|
1450
|
+
function Rule() {
|
|
1451
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
1452
|
+
flexShrink: 0,
|
|
1453
|
+
borderStyle: "single",
|
|
1454
|
+
borderTop: true,
|
|
1455
|
+
borderBottom: false,
|
|
1456
|
+
borderLeft: false,
|
|
1457
|
+
borderRight: false,
|
|
1458
|
+
borderDimColor: true
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1424
1462
|
//#endregion
|
|
1425
1463
|
//#region src/ui/filterCursor.ts
|
|
1426
1464
|
const INITIAL_CURSOR = {
|
|
@@ -1453,16 +1491,19 @@ function focusedIn(cursor, group, count) {
|
|
|
1453
1491
|
//#endregion
|
|
1454
1492
|
//#region src/ui/hooks/useDevState.ts
|
|
1455
1493
|
function useDevState(store) {
|
|
1456
|
-
|
|
1494
|
+
const subscribe = useCallback((onChange) => {
|
|
1457
1495
|
store.on("change", onChange);
|
|
1458
1496
|
return () => {
|
|
1459
1497
|
store.off("change", onChange);
|
|
1460
1498
|
};
|
|
1461
|
-
}, [store])
|
|
1499
|
+
}, [store]);
|
|
1500
|
+
const getSnapshot = useCallback(() => store.getState(), [store]);
|
|
1501
|
+
return useSyncExternalStore(subscribe, getSnapshot);
|
|
1462
1502
|
}
|
|
1463
1503
|
|
|
1464
1504
|
//#endregion
|
|
1465
1505
|
//#region src/ui/stores/LogStore.ts
|
|
1506
|
+
const STACK_FRAME = /^\s+at\s/;
|
|
1466
1507
|
const UPDATE_DEBOUNCE_MS = 30;
|
|
1467
1508
|
const ESC = String.fromCharCode(27);
|
|
1468
1509
|
var LogStore = class LogStore extends TypedEventEmitter {
|
|
@@ -1494,15 +1535,19 @@ var LogStore = class LogStore extends TypedEventEmitter {
|
|
|
1494
1535
|
}
|
|
1495
1536
|
onLog(record) {
|
|
1496
1537
|
const lines = formatBody(record).split(/\r\n|\r|\n/);
|
|
1497
|
-
for (const [index, line] of lines.entries())
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1538
|
+
for (const [index, line] of lines.entries()) {
|
|
1539
|
+
const text = line.replaceAll(/\p{Cc}/gu, (char) => char === ESC ? char : "");
|
|
1540
|
+
this.buffer.push({
|
|
1541
|
+
id: this.nextId++,
|
|
1542
|
+
channel: record.channel,
|
|
1543
|
+
level: record.level,
|
|
1544
|
+
label: record.label,
|
|
1545
|
+
head: index === 0,
|
|
1546
|
+
frame: index > 0 && STACK_FRAME.test(text),
|
|
1547
|
+
text,
|
|
1548
|
+
timestamp: record.timestamp
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1506
1551
|
this.scheduleUpdate();
|
|
1507
1552
|
}
|
|
1508
1553
|
getLogs() {
|
|
@@ -1559,12 +1604,14 @@ function filterLogs(logs, channels, levels) {
|
|
|
1559
1604
|
}
|
|
1560
1605
|
function useLogs(channels, levels) {
|
|
1561
1606
|
const store = LogStore.instance;
|
|
1562
|
-
const
|
|
1607
|
+
const subscribe = useCallback((cb) => {
|
|
1563
1608
|
store.on("change", cb);
|
|
1564
1609
|
return () => {
|
|
1565
1610
|
store.off("change", cb);
|
|
1566
1611
|
};
|
|
1567
|
-
}, [store])
|
|
1612
|
+
}, [store]);
|
|
1613
|
+
const getSnapshot = useCallback(() => store.getLogs(), [store]);
|
|
1614
|
+
const allLogs = useSyncExternalStore(subscribe, getSnapshot);
|
|
1568
1615
|
return useMemo(() => filterLogs(allLogs, channels ?? EMPTY_CHANNELS, levels ?? EMPTY_LEVELS), [
|
|
1569
1616
|
allLogs,
|
|
1570
1617
|
channels,
|
|
@@ -1573,22 +1620,22 @@ function useLogs(channels, levels) {
|
|
|
1573
1620
|
}
|
|
1574
1621
|
|
|
1575
1622
|
//#endregion
|
|
1576
|
-
//#region src/ui/hooks/
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
*/
|
|
1584
|
-
function useMeasuredHeight(ref) {
|
|
1585
|
-
const [height, setHeight] = useState(0);
|
|
1623
|
+
//#region src/ui/hooks/useMeasuredBox.ts
|
|
1624
|
+
const UNMEASURED = {
|
|
1625
|
+
width: 0,
|
|
1626
|
+
height: 0
|
|
1627
|
+
};
|
|
1628
|
+
function useMeasuredBox(ref) {
|
|
1629
|
+
const [box, setBox] = useState(UNMEASURED);
|
|
1586
1630
|
useLayoutEffect(() => {
|
|
1587
1631
|
if (!ref.current) return;
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1632
|
+
const { width, height } = measureElement(ref.current);
|
|
1633
|
+
setBox((prev) => prev.width === width && prev.height === height ? prev : {
|
|
1634
|
+
width,
|
|
1635
|
+
height
|
|
1636
|
+
});
|
|
1590
1637
|
});
|
|
1591
|
-
return
|
|
1638
|
+
return box;
|
|
1592
1639
|
}
|
|
1593
1640
|
|
|
1594
1641
|
//#endregion
|
|
@@ -1856,21 +1903,20 @@ function toggleLevel(prev, level) {
|
|
|
1856
1903
|
return base.size === FILTER_LEVELS.length ? /* @__PURE__ */ new Set() : base;
|
|
1857
1904
|
}
|
|
1858
1905
|
function soloChannel(prev, channel) {
|
|
1859
|
-
return prev.size === 1 && prev.has(channel) ? /* @__PURE__ */ new Set() : new Set([channel]);
|
|
1906
|
+
return prev.size === 1 && prev.has(channel) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([channel]);
|
|
1860
1907
|
}
|
|
1861
1908
|
function soloLevel(prev, level) {
|
|
1862
|
-
return prev.size === 1 && prev.has(level) ? /* @__PURE__ */ new Set() : new Set([level]);
|
|
1909
|
+
return prev.size === 1 && prev.has(level) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([level]);
|
|
1863
1910
|
}
|
|
1864
1911
|
function quit(ctx) {
|
|
1865
1912
|
ctx.store.beginQuit();
|
|
1866
1913
|
ctx.onQuit?.();
|
|
1867
1914
|
}
|
|
1868
1915
|
function handleQuitSignal(ctx) {
|
|
1869
|
-
if (ctx.key.ctrl
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
return false;
|
|
1916
|
+
if (!ctx.key.ctrl || ctx.input !== "c") return false;
|
|
1917
|
+
if (ctx.state.quitArmed) quit(ctx);
|
|
1918
|
+
else ctx.store.setQuitArmed(true);
|
|
1919
|
+
return true;
|
|
1874
1920
|
}
|
|
1875
1921
|
function handlePrompt(ctx) {
|
|
1876
1922
|
if (!ctx.state.commandUpdatePrompt) return false;
|
|
@@ -1934,14 +1980,14 @@ function handleActions(ctx) {
|
|
|
1934
1980
|
store.beginRestart();
|
|
1935
1981
|
ctx.onRestart?.();
|
|
1936
1982
|
break;
|
|
1937
|
-
case "
|
|
1983
|
+
case "c":
|
|
1938
1984
|
LogStore.instance.clear();
|
|
1939
1985
|
ctx.scroll.toBottom();
|
|
1940
|
-
break;
|
|
1941
1986
|
}
|
|
1942
1987
|
}
|
|
1943
1988
|
function dispatchHotkey(ctx) {
|
|
1944
1989
|
if (handleQuitSignal(ctx)) return;
|
|
1990
|
+
if (ctx.state.quitArmed) ctx.store.setQuitArmed(false);
|
|
1945
1991
|
if (handlePrompt(ctx)) return;
|
|
1946
1992
|
if (handleFilters(ctx)) return;
|
|
1947
1993
|
if (handleScroll(ctx)) return;
|
|
@@ -1968,20 +2014,6 @@ function NotificationStack({ notices }) {
|
|
|
1968
2014
|
}, notices.map((notice) => /* @__PURE__ */ React.createElement(Fragment, { key: notice.key }, notice.card)));
|
|
1969
2015
|
}
|
|
1970
2016
|
|
|
1971
|
-
//#endregion
|
|
1972
|
-
//#region src/ui/components/primitives/Rule.tsx
|
|
1973
|
-
function Rule() {
|
|
1974
|
-
return /* @__PURE__ */ React.createElement(Box, {
|
|
1975
|
-
flexShrink: 0,
|
|
1976
|
-
borderStyle: "single",
|
|
1977
|
-
borderTop: true,
|
|
1978
|
-
borderBottom: false,
|
|
1979
|
-
borderLeft: false,
|
|
1980
|
-
borderRight: false,
|
|
1981
|
-
borderDimColor: true
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
2017
|
//#endregion
|
|
1986
2018
|
//#region src/ui/format.ts
|
|
1987
2019
|
const MS_PER_SECOND = 1e3;
|
|
@@ -2005,6 +2037,74 @@ function formatClock(ts) {
|
|
|
2005
2037
|
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
2006
2038
|
}
|
|
2007
2039
|
|
|
2040
|
+
//#endregion
|
|
2041
|
+
//#region src/ui/logRows.ts
|
|
2042
|
+
const DOT_WIDTH = 1;
|
|
2043
|
+
const BAR_WIDTH = 1;
|
|
2044
|
+
const MIN_TEXT_WIDTH = 8;
|
|
2045
|
+
const messageColumn = (labelWidth) => 13 + labelWidth + 1 + DOT_WIDTH + 1;
|
|
2046
|
+
function isBlockLine(entry) {
|
|
2047
|
+
return entry !== void 0 && entry.level !== "error";
|
|
2048
|
+
}
|
|
2049
|
+
function opensBlock(logs, index) {
|
|
2050
|
+
const log = logs[index];
|
|
2051
|
+
const next = logs[index + 1];
|
|
2052
|
+
return log?.head === true && isBlockLine(log) && next !== void 0 && !next.head && isBlockLine(next);
|
|
2053
|
+
}
|
|
2054
|
+
function closesBlock(logs, index) {
|
|
2055
|
+
const log = logs[index];
|
|
2056
|
+
const next = logs[index + 1];
|
|
2057
|
+
return log !== void 0 && !log.head && isBlockLine(log) && (next === void 0 || next.head);
|
|
2058
|
+
}
|
|
2059
|
+
function prevClosedBlock(logs, index) {
|
|
2060
|
+
const prev = logs[index - 1];
|
|
2061
|
+
return prev !== void 0 && !prev.head && isBlockLine(prev);
|
|
2062
|
+
}
|
|
2063
|
+
function expandRows(logs) {
|
|
2064
|
+
const rows = [];
|
|
2065
|
+
for (const [index, entry] of logs.entries()) {
|
|
2066
|
+
if (opensBlock(logs, index) && !prevClosedBlock(logs, index)) rows.push({
|
|
2067
|
+
kind: "rule",
|
|
2068
|
+
key: `top-${entry.id}`
|
|
2069
|
+
});
|
|
2070
|
+
rows.push({
|
|
2071
|
+
kind: "entry",
|
|
2072
|
+
key: String(entry.id),
|
|
2073
|
+
entry,
|
|
2074
|
+
text: entry.text,
|
|
2075
|
+
first: true
|
|
2076
|
+
});
|
|
2077
|
+
if (closesBlock(logs, index)) rows.push({
|
|
2078
|
+
kind: "rule",
|
|
2079
|
+
key: `bottom-${entry.id}`
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
return rows;
|
|
2083
|
+
}
|
|
2084
|
+
function textWidth(entry, width, labelWidth) {
|
|
2085
|
+
return width - (!entry.head && entry.level === "error" ? BAR_WIDTH : messageColumn(labelWidth));
|
|
2086
|
+
}
|
|
2087
|
+
function wrapRows(rows, width, labelWidth) {
|
|
2088
|
+
return rows.flatMap((row) => {
|
|
2089
|
+
if (row.kind === "rule") return row;
|
|
2090
|
+
const available = textWidth(row.entry, width, labelWidth);
|
|
2091
|
+
if (available < MIN_TEXT_WIDTH) return row;
|
|
2092
|
+
return wrapAnsi(row.text, available, {
|
|
2093
|
+
hard: true,
|
|
2094
|
+
trim: false
|
|
2095
|
+
}).split("\n").map((text, index) => ({
|
|
2096
|
+
kind: "entry",
|
|
2097
|
+
key: `${row.key}:${String(index)}`,
|
|
2098
|
+
entry: row.entry,
|
|
2099
|
+
text: index === 0 ? text : text.replace(/^ +/, ""),
|
|
2100
|
+
first: index === 0
|
|
2101
|
+
}));
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
function rowKey(row) {
|
|
2105
|
+
return row.key;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2008
2108
|
//#endregion
|
|
2009
2109
|
//#region src/ui/components/primitives/ScrollableLogView.tsx
|
|
2010
2110
|
const MIN_LOG_LINES = 5;
|
|
@@ -2014,8 +2114,6 @@ const BAR = "▌";
|
|
|
2014
2114
|
const GUIDE = "│";
|
|
2015
2115
|
const ELLIPSIS = "…";
|
|
2016
2116
|
const CHIP_TEXT = "#1a1a1a";
|
|
2017
|
-
const DOT_WIDTH = 1;
|
|
2018
|
-
const messageColumn = (labelWidth) => 13 + labelWidth + 1 + DOT_WIDTH + 1;
|
|
2019
2117
|
const LEVEL_LETTER = {
|
|
2020
2118
|
error: "E",
|
|
2021
2119
|
warn: "W",
|
|
@@ -2041,23 +2139,26 @@ function Prefix({ log, labelWidth }) {
|
|
|
2041
2139
|
const label = truncate(log.label, labelWidth).padStart(labelWidth);
|
|
2042
2140
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Chip, { level: log.level }), " ", /* @__PURE__ */ React.createElement(Text, { dimColor: true }, formatClock(log.timestamp)), " ", /* @__PURE__ */ React.createElement(Text, null, label), " ", /* @__PURE__ */ React.createElement(Text, { color: channelColor(log.channel) }, DOT));
|
|
2043
2141
|
}
|
|
2044
|
-
function HeadLine({ log, labelWidth }) {
|
|
2142
|
+
function HeadLine({ log, text, labelWidth }) {
|
|
2045
2143
|
const wash = WASH[log.level];
|
|
2046
2144
|
if (wash) return /* @__PURE__ */ React.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ React.createElement(Text, { backgroundColor: wash }, /* @__PURE__ */ React.createElement(Prefix, {
|
|
2047
2145
|
log,
|
|
2048
2146
|
labelWidth
|
|
2049
|
-
})), " ",
|
|
2147
|
+
})), " ", text);
|
|
2050
2148
|
return /* @__PURE__ */ React.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ React.createElement(Prefix, {
|
|
2051
2149
|
log,
|
|
2052
2150
|
labelWidth
|
|
2053
|
-
}), " ",
|
|
2151
|
+
}), " ", text);
|
|
2152
|
+
}
|
|
2153
|
+
function HeadTail({ text, labelWidth }) {
|
|
2154
|
+
return /* @__PURE__ */ React.createElement(Text, { wrap: "truncate" }, " ".repeat(messageColumn(labelWidth) - 2), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, GUIDE), " ", text);
|
|
2054
2155
|
}
|
|
2055
|
-
function ContinuationLine({ log, labelWidth }) {
|
|
2156
|
+
function ContinuationLine({ log, text, labelWidth }) {
|
|
2056
2157
|
if (log.level === "error") return /* @__PURE__ */ React.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ React.createElement(Text, {
|
|
2057
2158
|
color: LEVEL_COLOR.error,
|
|
2058
2159
|
bold: true
|
|
2059
|
-
}, BAR), /* @__PURE__ */ React.createElement(Text, { dimColor:
|
|
2060
|
-
return /* @__PURE__ */ React.createElement(Text, { wrap: "truncate" }, " ".repeat(messageColumn(labelWidth) - 2), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, GUIDE), " ",
|
|
2160
|
+
}, BAR), /* @__PURE__ */ React.createElement(Text, { dimColor: log.frame }, text));
|
|
2161
|
+
return /* @__PURE__ */ React.createElement(Text, { wrap: "truncate" }, " ".repeat(messageColumn(labelWidth) - 2), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, GUIDE), " ", text);
|
|
2061
2162
|
}
|
|
2062
2163
|
function ScrollableLogView({ visible, viewportHeight, measured }) {
|
|
2063
2164
|
if (!measured) return null;
|
|
@@ -2077,15 +2178,25 @@ function ScrollableLogView({ visible, viewportHeight, measured }) {
|
|
|
2077
2178
|
flexGrow: 1,
|
|
2078
2179
|
justifyContent: "flex-end",
|
|
2079
2180
|
overflow: "hidden"
|
|
2080
|
-
}, visible.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Waiting for logs…") : visible.map((row) =>
|
|
2081
|
-
key: row.key
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2181
|
+
}, visible.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Waiting for logs…") : visible.map((row) => {
|
|
2182
|
+
if (row.kind === "rule") return /* @__PURE__ */ React.createElement(Rule, { key: row.key });
|
|
2183
|
+
const props = {
|
|
2184
|
+
log: row.entry,
|
|
2185
|
+
text: row.text,
|
|
2186
|
+
labelWidth
|
|
2187
|
+
};
|
|
2188
|
+
if (!row.entry.head) return /* @__PURE__ */ React.createElement(ContinuationLine, {
|
|
2189
|
+
key: row.key,
|
|
2190
|
+
...props
|
|
2191
|
+
});
|
|
2192
|
+
return row.first ? /* @__PURE__ */ React.createElement(HeadLine, {
|
|
2193
|
+
key: row.key,
|
|
2194
|
+
...props
|
|
2195
|
+
}) : /* @__PURE__ */ React.createElement(HeadTail, {
|
|
2196
|
+
key: row.key,
|
|
2197
|
+
...props
|
|
2198
|
+
});
|
|
2199
|
+
}));
|
|
2089
2200
|
}
|
|
2090
2201
|
|
|
2091
2202
|
//#endregion
|
|
@@ -2116,8 +2227,14 @@ function BlinkDot() {
|
|
|
2116
2227
|
function Glyph({ phase, glyph }) {
|
|
2117
2228
|
const meta = PHASE_META[phase];
|
|
2118
2229
|
if (glyph === "live") return isStreaming(phase) ? /* @__PURE__ */ React.createElement(BlinkDot, null) : /* @__PURE__ */ React.createElement(Text, null, meta.icon);
|
|
2119
|
-
if (meta.kind === "spinner") return /* @__PURE__ */ React.createElement(Spinner, {
|
|
2120
|
-
|
|
2230
|
+
if (meta.kind === "spinner") return /* @__PURE__ */ React.createElement(Spinner, {
|
|
2231
|
+
key: "balloon2",
|
|
2232
|
+
type: "balloon2"
|
|
2233
|
+
});
|
|
2234
|
+
if (meta.kind === "arc") return /* @__PURE__ */ React.createElement(Spinner, {
|
|
2235
|
+
key: "toggle4",
|
|
2236
|
+
type: "toggle4"
|
|
2237
|
+
});
|
|
2121
2238
|
return /* @__PURE__ */ React.createElement(Text, null, meta.icon);
|
|
2122
2239
|
}
|
|
2123
2240
|
function StatusBadge({ phase, glyph = "phase" }) {
|
|
@@ -2141,7 +2258,7 @@ function Hotkey({ keyLabel, action, enabled = true, highlight = false }) {
|
|
|
2141
2258
|
bold: true
|
|
2142
2259
|
}, keyLabel), /* @__PURE__ */ React.createElement(Text, { color: actionColor }, " ", action));
|
|
2143
2260
|
}
|
|
2144
|
-
const RESTART_HINT_PHASES = new Set([
|
|
2261
|
+
const RESTART_HINT_PHASES = /* @__PURE__ */ new Set([
|
|
2145
2262
|
"restart-required",
|
|
2146
2263
|
"disconnected",
|
|
2147
2264
|
"error"
|
|
@@ -2160,7 +2277,7 @@ function SessionKeys({ phase, interactive, following }) {
|
|
|
2160
2277
|
action: "disconnect",
|
|
2161
2278
|
enabled: interactive && isSessionLive(phase)
|
|
2162
2279
|
}), /* @__PURE__ */ React.createElement(Hotkey, {
|
|
2163
|
-
keyLabel: "
|
|
2280
|
+
keyLabel: "c",
|
|
2164
2281
|
action: "clear",
|
|
2165
2282
|
enabled: interactive
|
|
2166
2283
|
}), /* @__PURE__ */ React.createElement(Hotkey, {
|
|
@@ -2191,15 +2308,24 @@ function FilterKeys() {
|
|
|
2191
2308
|
//#endregion
|
|
2192
2309
|
//#region src/ui/components/primitives/Sidebar.tsx
|
|
2193
2310
|
const MAX_RAIL = 40;
|
|
2194
|
-
const META_LABEL_WIDTH =
|
|
2311
|
+
const META_LABEL_WIDTH = 7;
|
|
2195
2312
|
const LOG_DIR = "logs/";
|
|
2196
2313
|
function Meta({ label, value }) {
|
|
2197
2314
|
return /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, label.padEnd(META_LABEL_WIDTH)), value);
|
|
2198
2315
|
}
|
|
2316
|
+
function tunnelValue(status) {
|
|
2317
|
+
return status === "live" || status === "lost" ? status : `${status}…`;
|
|
2318
|
+
}
|
|
2199
2319
|
function StatusBlock({ state, uptimeMs }) {
|
|
2200
2320
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(StatusBadge, { phase: state.phase }), state.status ? /* @__PURE__ */ React.createElement(Text, null, state.status) : null, uptimeMs === null ? null : /* @__PURE__ */ React.createElement(Meta, {
|
|
2201
2321
|
label: "up",
|
|
2202
2322
|
value: formatUptime(uptimeMs)
|
|
2323
|
+
}), state.port === null ? null : /* @__PURE__ */ React.createElement(Meta, {
|
|
2324
|
+
label: "port",
|
|
2325
|
+
value: String(state.port)
|
|
2326
|
+
}), state.tunnel === null ? null : /* @__PURE__ */ React.createElement(Meta, {
|
|
2327
|
+
label: "tunnel",
|
|
2328
|
+
value: tunnelValue(state.tunnel)
|
|
2203
2329
|
}), /* @__PURE__ */ React.createElement(Meta, {
|
|
2204
2330
|
label: "logs",
|
|
2205
2331
|
value: LOG_DIR
|
|
@@ -2256,7 +2382,8 @@ function StatusLine({ state, notices, columns }) {
|
|
|
2256
2382
|
const [top, ...rest] = notices;
|
|
2257
2383
|
return /* @__PURE__ */ React.createElement(Box, {
|
|
2258
2384
|
flexShrink: 0,
|
|
2259
|
-
justifyContent: "space-between"
|
|
2385
|
+
justifyContent: "space-between",
|
|
2386
|
+
paddingX: 1
|
|
2260
2387
|
}, /* @__PURE__ */ React.createElement(Box, {
|
|
2261
2388
|
flexShrink: 1,
|
|
2262
2389
|
overflow: "hidden"
|
|
@@ -2320,48 +2447,6 @@ function DevLayout(props) {
|
|
|
2320
2447
|
})), railed ? /* @__PURE__ */ React.createElement(NotificationStack, { notices }) : null));
|
|
2321
2448
|
}
|
|
2322
2449
|
|
|
2323
|
-
//#endregion
|
|
2324
|
-
//#region src/ui/logRows.ts
|
|
2325
|
-
function isBlockLine(entry) {
|
|
2326
|
-
return entry !== void 0 && entry.level !== "error";
|
|
2327
|
-
}
|
|
2328
|
-
function opensBlock(logs, index) {
|
|
2329
|
-
const log = logs[index];
|
|
2330
|
-
const next = logs[index + 1];
|
|
2331
|
-
return log?.head === true && isBlockLine(log) && next !== void 0 && !next.head && isBlockLine(next);
|
|
2332
|
-
}
|
|
2333
|
-
function closesBlock(logs, index) {
|
|
2334
|
-
const log = logs[index];
|
|
2335
|
-
const next = logs[index + 1];
|
|
2336
|
-
return log !== void 0 && !log.head && isBlockLine(log) && (next === void 0 || next.head);
|
|
2337
|
-
}
|
|
2338
|
-
function prevClosedBlock(logs, index) {
|
|
2339
|
-
const prev = logs[index - 1];
|
|
2340
|
-
return prev !== void 0 && !prev.head && isBlockLine(prev);
|
|
2341
|
-
}
|
|
2342
|
-
function expandRows(logs) {
|
|
2343
|
-
const rows = [];
|
|
2344
|
-
for (const [index, entry] of logs.entries()) {
|
|
2345
|
-
if (opensBlock(logs, index) && !prevClosedBlock(logs, index)) rows.push({
|
|
2346
|
-
kind: "rule",
|
|
2347
|
-
key: `top-${entry.id}`
|
|
2348
|
-
});
|
|
2349
|
-
rows.push({
|
|
2350
|
-
kind: "entry",
|
|
2351
|
-
key: entry.id,
|
|
2352
|
-
entry
|
|
2353
|
-
});
|
|
2354
|
-
if (closesBlock(logs, index)) rows.push({
|
|
2355
|
-
kind: "rule",
|
|
2356
|
-
key: `bottom-${entry.id}`
|
|
2357
|
-
});
|
|
2358
|
-
}
|
|
2359
|
-
return rows;
|
|
2360
|
-
}
|
|
2361
|
-
function rowKey(row) {
|
|
2362
|
-
return row.key;
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
2450
|
//#endregion
|
|
2366
2451
|
//#region src/ui/components/CommandRefreshPrompt.tsx
|
|
2367
2452
|
function CommandRefreshPrompt({ files }) {
|
|
@@ -2407,6 +2492,23 @@ function ErrorDisplay({ error }) {
|
|
|
2407
2492
|
}, "r"), " ", "to restart")));
|
|
2408
2493
|
}
|
|
2409
2494
|
|
|
2495
|
+
//#endregion
|
|
2496
|
+
//#region src/ui/components/QuitConfirmCard.tsx
|
|
2497
|
+
function QuitConfirmCard() {
|
|
2498
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
2499
|
+
borderStyle: "round",
|
|
2500
|
+
borderColor: ui.warn,
|
|
2501
|
+
flexDirection: "column",
|
|
2502
|
+
paddingX: 1
|
|
2503
|
+
}, /* @__PURE__ */ React.createElement(Text, {
|
|
2504
|
+
color: ui.warn,
|
|
2505
|
+
bold: true
|
|
2506
|
+
}, "Quit?"), /* @__PURE__ */ React.createElement(Text, null, "press", " ", /* @__PURE__ */ React.createElement(Text, {
|
|
2507
|
+
color: ui.warn,
|
|
2508
|
+
bold: true
|
|
2509
|
+
}, "Ctrl-C"), " ", "again to quit, or any other key to stay"));
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2410
2512
|
//#endregion
|
|
2411
2513
|
//#region src/ui/components/RestartRequiredCard.tsx
|
|
2412
2514
|
function RestartRequiredCard() {
|
|
@@ -2424,12 +2526,52 @@ function RestartRequiredCard() {
|
|
|
2424
2526
|
}, "r"), " ", "to restart"));
|
|
2425
2527
|
}
|
|
2426
2528
|
|
|
2529
|
+
//#endregion
|
|
2530
|
+
//#region src/ui/components/TunnelOpeningCard.tsx
|
|
2531
|
+
const STEPS = [
|
|
2532
|
+
["opening", "Opening the tunnel"],
|
|
2533
|
+
["resolving", "Waiting for it to answer"],
|
|
2534
|
+
["registering", "Telling Discord where to reach you"]
|
|
2535
|
+
];
|
|
2536
|
+
function Marker({ done, active }) {
|
|
2537
|
+
if (done) return /* @__PURE__ */ React.createElement(Text, { color: ui.good }, "✔︎");
|
|
2538
|
+
if (active) return /* @__PURE__ */ React.createElement(Spinner, { type: "dots" });
|
|
2539
|
+
return /* @__PURE__ */ React.createElement(Text, { color: ui.faint }, "·");
|
|
2540
|
+
}
|
|
2541
|
+
function TunnelOpeningCard({ phase }) {
|
|
2542
|
+
const at = STEPS.findIndex(([step]) => step === phase);
|
|
2543
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
2544
|
+
borderStyle: "round",
|
|
2545
|
+
borderColor: ui.accent,
|
|
2546
|
+
flexDirection: "column",
|
|
2547
|
+
paddingX: 1
|
|
2548
|
+
}, /* @__PURE__ */ React.createElement(Text, {
|
|
2549
|
+
color: ui.accent,
|
|
2550
|
+
bold: true
|
|
2551
|
+
}, "Setting up your interactions endpoint"), STEPS.map(([step, label], index) => /* @__PURE__ */ React.createElement(Text, {
|
|
2552
|
+
key: step,
|
|
2553
|
+
dimColor: index > at
|
|
2554
|
+
}, /* @__PURE__ */ React.createElement(Marker, {
|
|
2555
|
+
done: index < at,
|
|
2556
|
+
active: index === at
|
|
2557
|
+
}), " ", label)));
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2427
2560
|
//#endregion
|
|
2428
2561
|
//#region src/ui/notices.tsx
|
|
2429
2562
|
const RESTART = "press r to restart";
|
|
2563
|
+
function openingPhase(status) {
|
|
2564
|
+
return status === null || status === "live" || status === "lost" ? null : status;
|
|
2565
|
+
}
|
|
2430
2566
|
/** Every pending notice, ranked with the one that blocks a decision first. */
|
|
2431
2567
|
function noticesOf(state) {
|
|
2432
2568
|
const notices = [];
|
|
2569
|
+
if (state.quitArmed) notices.push({
|
|
2570
|
+
key: "quit",
|
|
2571
|
+
summary: "Quit?",
|
|
2572
|
+
action: "press Ctrl-C again",
|
|
2573
|
+
card: /* @__PURE__ */ React.createElement(QuitConfirmCard, null)
|
|
2574
|
+
});
|
|
2433
2575
|
if (state.commandUpdatePrompt) {
|
|
2434
2576
|
const count = state.commandUpdatePrompt.length;
|
|
2435
2577
|
notices.push({
|
|
@@ -2451,6 +2593,13 @@ function noticesOf(state) {
|
|
|
2451
2593
|
action: RESTART,
|
|
2452
2594
|
card: /* @__PURE__ */ React.createElement(RestartRequiredCard, null)
|
|
2453
2595
|
});
|
|
2596
|
+
const phase = openingPhase(state.tunnel);
|
|
2597
|
+
if (phase) notices.push({
|
|
2598
|
+
key: "tunnel",
|
|
2599
|
+
summary: "Setting up your interactions endpoint",
|
|
2600
|
+
action: "hold on",
|
|
2601
|
+
card: /* @__PURE__ */ React.createElement(TunnelOpeningCard, { phase })
|
|
2602
|
+
});
|
|
2454
2603
|
return notices;
|
|
2455
2604
|
}
|
|
2456
2605
|
|
|
@@ -2476,14 +2625,14 @@ function DevApp(props) {
|
|
|
2476
2625
|
const [enabledLevels, setEnabledLevels] = useState(() => /* @__PURE__ */ new Set());
|
|
2477
2626
|
const [cursor, setCursor] = useState(INITIAL_CURSOR);
|
|
2478
2627
|
const logBoxRef = useRef(null);
|
|
2479
|
-
const
|
|
2628
|
+
const logBox = useMeasuredBox(logBoxRef);
|
|
2480
2629
|
const railRef = useRef(null);
|
|
2481
2630
|
const railWidth = useRailWidth(railRef, state.phase, rows, columns);
|
|
2482
2631
|
const tier = tierFor(rows, columns);
|
|
2483
2632
|
const notices = noticesOf(state);
|
|
2484
2633
|
const logs = useLogs(enabled, enabledLevels);
|
|
2485
|
-
const logRows = useMemo(() => expandRows(logs), [logs]);
|
|
2486
|
-
const viewportHeight = Math.max(1,
|
|
2634
|
+
const logRows = useMemo(() => wrapRows(expandRows(logs), logBox.width, Math.max(1, LogStore.instance.getLabelWidth())), [logs, logBox.width]);
|
|
2635
|
+
const viewportHeight = Math.max(1, logBox.height);
|
|
2487
2636
|
const scroll = useScroll(logRows, viewportHeight, rowKey);
|
|
2488
2637
|
const uptimeMs = useUptime(state);
|
|
2489
2638
|
const interactive = !state.isBusy || state.restartRequired;
|
|
@@ -2524,7 +2673,7 @@ function DevApp(props) {
|
|
|
2524
2673
|
width: columns,
|
|
2525
2674
|
height: rows,
|
|
2526
2675
|
overflow: "hidden"
|
|
2527
|
-
}, /* @__PURE__ */ React.createElement(DevLayout, {
|
|
2676
|
+
}, /* @__PURE__ */ React.createElement(Rule, null), /* @__PURE__ */ React.createElement(DevLayout, {
|
|
2528
2677
|
state,
|
|
2529
2678
|
tier,
|
|
2530
2679
|
notices,
|
|
@@ -2533,7 +2682,7 @@ function DevApp(props) {
|
|
|
2533
2682
|
logBoxRef,
|
|
2534
2683
|
scroll,
|
|
2535
2684
|
viewportHeight,
|
|
2536
|
-
measured:
|
|
2685
|
+
measured: logBox.height > 0,
|
|
2537
2686
|
columns,
|
|
2538
2687
|
enabled,
|
|
2539
2688
|
enabledLevels,
|
|
@@ -2552,7 +2701,10 @@ const INITIAL = {
|
|
|
2552
2701
|
isBusy: true,
|
|
2553
2702
|
frameworkVersion: null,
|
|
2554
2703
|
restartRequired: false,
|
|
2555
|
-
commandUpdatePrompt: null
|
|
2704
|
+
commandUpdatePrompt: null,
|
|
2705
|
+
port: null,
|
|
2706
|
+
tunnel: null,
|
|
2707
|
+
quitArmed: false
|
|
2556
2708
|
};
|
|
2557
2709
|
var DevStore = class extends TypedEventEmitter {
|
|
2558
2710
|
state = INITIAL;
|
|
@@ -2574,6 +2726,12 @@ var DevStore = class extends TypedEventEmitter {
|
|
|
2574
2726
|
setFrameworkVersion(frameworkVersion) {
|
|
2575
2727
|
this.patch({ frameworkVersion });
|
|
2576
2728
|
}
|
|
2729
|
+
setTunnel(tunnel) {
|
|
2730
|
+
this.patch({ tunnel });
|
|
2731
|
+
}
|
|
2732
|
+
setQuitArmed(quitArmed) {
|
|
2733
|
+
this.patch({ quitArmed });
|
|
2734
|
+
}
|
|
2577
2735
|
clearPrompt() {
|
|
2578
2736
|
this.patch({ commandUpdatePrompt: null });
|
|
2579
2737
|
}
|
|
@@ -2583,7 +2741,8 @@ var DevStore = class extends TypedEventEmitter {
|
|
|
2583
2741
|
isBusy: true,
|
|
2584
2742
|
restartRequired: false,
|
|
2585
2743
|
error: null,
|
|
2586
|
-
status: "Restarting…"
|
|
2744
|
+
status: "Restarting…",
|
|
2745
|
+
port: null
|
|
2587
2746
|
});
|
|
2588
2747
|
}
|
|
2589
2748
|
beginDisconnect() {
|
|
@@ -2592,7 +2751,8 @@ var DevStore = class extends TypedEventEmitter {
|
|
|
2592
2751
|
isBusy: true,
|
|
2593
2752
|
restartRequired: false,
|
|
2594
2753
|
error: null,
|
|
2595
|
-
status: "Disconnecting…"
|
|
2754
|
+
status: "Disconnecting…",
|
|
2755
|
+
port: null
|
|
2596
2756
|
});
|
|
2597
2757
|
}
|
|
2598
2758
|
beginQuit() {
|
|
@@ -2614,6 +2774,9 @@ var DevStore = class extends TypedEventEmitter {
|
|
|
2614
2774
|
case "command-update-prompt":
|
|
2615
2775
|
this.patch({ commandUpdatePrompt: event.files });
|
|
2616
2776
|
break;
|
|
2777
|
+
case "server-listening":
|
|
2778
|
+
this.patch({ port: event.port });
|
|
2779
|
+
break;
|
|
2617
2780
|
case "module-loading":
|
|
2618
2781
|
case "module-loaded":
|
|
2619
2782
|
case "module-error":
|
|
@@ -2632,65 +2795,251 @@ var DevStore = class extends TypedEventEmitter {
|
|
|
2632
2795
|
};
|
|
2633
2796
|
|
|
2634
2797
|
//#endregion
|
|
2635
|
-
//#region src/commands/dev/
|
|
2636
|
-
const
|
|
2637
|
-
var
|
|
2638
|
-
|
|
2798
|
+
//#region src/commands/dev/TscRunner.ts
|
|
2799
|
+
const TSC_GRACEFUL_EXIT_MS = 2e3;
|
|
2800
|
+
var TscRunner = class {
|
|
2801
|
+
tsconfigPath;
|
|
2802
|
+
cwd;
|
|
2803
|
+
process = null;
|
|
2639
2804
|
logger;
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
return this.server?.environments.ssr.hot;
|
|
2645
|
-
}
|
|
2646
|
-
get dev() {
|
|
2647
|
-
return this.hot ? wrapHot(this.hot) : void 0;
|
|
2648
|
-
}
|
|
2649
|
-
constructor(config) {
|
|
2650
|
-
super();
|
|
2651
|
-
this.config = config;
|
|
2652
|
-
this.logger = new Logger("HMR", { channel: "hmr" });
|
|
2653
|
-
}
|
|
2654
|
-
get plugin() {
|
|
2655
|
-
return {
|
|
2656
|
-
name: "seedcord:hmr",
|
|
2657
|
-
configureServer: this.configureServer.bind(this),
|
|
2658
|
-
hotUpdate: this.hotUpdate.bind(this)
|
|
2659
|
-
};
|
|
2660
|
-
}
|
|
2661
|
-
sendRefreshCommands(shouldRefresh) {
|
|
2662
|
-
this.dev?.send("seedcord:refresh-commands", { shouldRefresh });
|
|
2805
|
+
constructor(tsconfigPath, cwd) {
|
|
2806
|
+
this.tsconfigPath = tsconfigPath;
|
|
2807
|
+
this.cwd = cwd;
|
|
2808
|
+
this.logger = new Logger("Typecheck", { channel: "tsc" });
|
|
2663
2809
|
}
|
|
2664
|
-
|
|
2665
|
-
this.
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2810
|
+
start() {
|
|
2811
|
+
if (this.process) return;
|
|
2812
|
+
const tscPath = this.resolveProjectTsc();
|
|
2813
|
+
if (!tscPath) {
|
|
2814
|
+
this.logger.error("Unable to resolve \"typescript\". Install it in the project to enable tsc --watch.");
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
const args = [
|
|
2818
|
+
tscPath,
|
|
2819
|
+
"--noEmit",
|
|
2820
|
+
"--watch",
|
|
2821
|
+
"--preserveWatchOutput",
|
|
2822
|
+
"--pretty"
|
|
2823
|
+
];
|
|
2824
|
+
if (this.tsconfigPath) args.push("--project", this.tsconfigPath);
|
|
2825
|
+
this.logger.debug("Starting tsc --watch...");
|
|
2826
|
+
this.process = spawn(process.execPath, args, {
|
|
2827
|
+
cwd: this.cwd ?? process.cwd(),
|
|
2828
|
+
stdio: [
|
|
2829
|
+
"ignore",
|
|
2830
|
+
"pipe",
|
|
2831
|
+
"pipe"
|
|
2832
|
+
]
|
|
2675
2833
|
});
|
|
2676
|
-
this.
|
|
2677
|
-
|
|
2678
|
-
this.logger.
|
|
2834
|
+
this.process.stdout?.on("data", (data) => {
|
|
2835
|
+
const lines = data.toString().split("\n");
|
|
2836
|
+
for (const line of lines) if (line.trim()) this.logger.debug(line.trimEnd());
|
|
2679
2837
|
});
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2838
|
+
this.process.stderr?.on("data", (data) => {
|
|
2839
|
+
const lines = data.toString().split("\n");
|
|
2840
|
+
for (const line of lines) if (line.trim()) this.logger.error(line.trimEnd());
|
|
2841
|
+
});
|
|
2842
|
+
this.process.on("error", (err) => {
|
|
2843
|
+
this.logger.error(`Failed to start tsc: ${err.message}`);
|
|
2844
|
+
});
|
|
2845
|
+
this.process.on("exit", (code) => {
|
|
2846
|
+
if (code !== 0 && code !== null) this.logger.error(`tsc exited with code ${code}`);
|
|
2847
|
+
else this.logger.debug("tsc exited.");
|
|
2848
|
+
this.process = null;
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
stop() {
|
|
2852
|
+
const child = this.process;
|
|
2853
|
+
if (!child) return;
|
|
2854
|
+
this.logger.debug("Stopping tsc...");
|
|
2855
|
+
child.kill("SIGTERM");
|
|
2856
|
+
const killTimer = setTimeout(() => child.kill("SIGKILL"), TSC_GRACEFUL_EXIT_MS);
|
|
2857
|
+
killTimer.unref();
|
|
2858
|
+
child.once("exit", () => {
|
|
2859
|
+
clearTimeout(killTimer);
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
resolveProjectTsc() {
|
|
2863
|
+
const projectDir = this.cwd ?? process.cwd();
|
|
2864
|
+
const manifest = resolve(projectDir, "package.json");
|
|
2865
|
+
const projectRequire = existsSync(manifest) ? createRequire(manifest) : createRequire(import.meta.url);
|
|
2866
|
+
try {
|
|
2867
|
+
return projectRequire.resolve("typescript/bin/tsc");
|
|
2868
|
+
} catch {
|
|
2869
|
+
return null;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
|
|
2874
|
+
//#endregion
|
|
2875
|
+
//#region src/commands/dev/DevSession.ts
|
|
2876
|
+
function isSeedcordInstance(candidate) {
|
|
2877
|
+
return typeof candidate === "object" && candidate !== null && candidate[SeedcordBrand] === true;
|
|
2878
|
+
}
|
|
2879
|
+
var DevSession = class {
|
|
2880
|
+
config;
|
|
2881
|
+
runtime;
|
|
2882
|
+
store;
|
|
2883
|
+
onEvent;
|
|
2884
|
+
stopResolve;
|
|
2885
|
+
instance;
|
|
2886
|
+
isStopped = false;
|
|
2887
|
+
startupPromise;
|
|
2888
|
+
tscRunner;
|
|
2889
|
+
stopPromise;
|
|
2890
|
+
constructor(config, runtime, store, onEvent) {
|
|
2891
|
+
this.config = config;
|
|
2892
|
+
this.runtime = runtime;
|
|
2893
|
+
this.store = store;
|
|
2894
|
+
this.onEvent = onEvent;
|
|
2895
|
+
}
|
|
2896
|
+
async loadInstanceModule() {
|
|
2897
|
+
await this.runtime.start({
|
|
2898
|
+
config: this.config,
|
|
2899
|
+
onEvent: (event) => {
|
|
2900
|
+
this.store.apply(event);
|
|
2901
|
+
this.onEvent(event);
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2904
|
+
if (!existsSync(this.config.instance)) throw new SeedcordError(SeedcordErrorCode.CliEntryNotFound, [this.config.instance]);
|
|
2905
|
+
try {
|
|
2906
|
+
const { module } = await this.runtime.loadEntry();
|
|
2907
|
+
return module;
|
|
2908
|
+
} catch (error) {
|
|
2909
|
+
const message = Error.isError(error) ? error.message : String(error);
|
|
2910
|
+
throw new SeedcordError(SeedcordErrorCode.CliStartFailed, [this.config.instance, message]);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
async start(onReady) {
|
|
2914
|
+
const cwd = dirname(this.config.configFile);
|
|
2915
|
+
this.tscRunner = new TscRunner(this.config.tsconfig, cwd);
|
|
2916
|
+
this.tscRunner.start();
|
|
2917
|
+
const module = await this.loadInstanceModule();
|
|
2918
|
+
const exported = resolveDefaultExport(module);
|
|
2919
|
+
const instance = await Promise.resolve(exported);
|
|
2920
|
+
if (!isSeedcordInstance(instance)) throw new SeedcordError(SeedcordErrorCode.CliInstanceInvalid);
|
|
2921
|
+
this.instance = instance;
|
|
2922
|
+
this.store.setFrameworkVersion(instance.version);
|
|
2923
|
+
try {
|
|
2924
|
+
this.store.setPhase("starting");
|
|
2925
|
+
this.store.setStatus("Starting Seedcord instance…");
|
|
2926
|
+
this.startupPromise = Promise.resolve(instance.start());
|
|
2927
|
+
await this.startupPromise;
|
|
2928
|
+
if (this.isStopped) return;
|
|
2929
|
+
this.store.setPhase("running");
|
|
2930
|
+
this.store.setStatus(`${chalk.bold(instance.username ?? "Bot")} is ready!`);
|
|
2931
|
+
onReady?.();
|
|
2932
|
+
await new Promise((resolve) => {
|
|
2933
|
+
this.stopResolve = resolve;
|
|
2934
|
+
});
|
|
2935
|
+
} catch (error) {
|
|
2936
|
+
const reason = Error.isError(error) ? error.message : "Unknown error";
|
|
2937
|
+
throw new SeedcordError(SeedcordErrorCode.CliStartFailed, [this.config.instance, reason]);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
async stop() {
|
|
2941
|
+
this.stopPromise ??= this.runStop();
|
|
2942
|
+
return this.stopPromise;
|
|
2943
|
+
}
|
|
2944
|
+
async runStop() {
|
|
2945
|
+
this.isStopped = true;
|
|
2946
|
+
this.tscRunner?.stop();
|
|
2947
|
+
this.instance?.startup.abort();
|
|
2948
|
+
if (this.startupPromise) try {
|
|
2949
|
+
await this.startupPromise;
|
|
2950
|
+
} catch {}
|
|
2951
|
+
await this.instance?.shutdown.run(0, false);
|
|
2952
|
+
this.stopResolve?.();
|
|
2953
|
+
}
|
|
2954
|
+
async dispose() {
|
|
2955
|
+
await this.stop();
|
|
2956
|
+
await this.runtime.dispose();
|
|
2957
|
+
}
|
|
2958
|
+
refreshCommands(shouldRefresh) {
|
|
2959
|
+
this.runtime.refreshCommands?.(shouldRefresh);
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
2962
|
+
|
|
2963
|
+
//#endregion
|
|
2964
|
+
//#region src/commands/dev/runtime/HmrPlugin.ts
|
|
2965
|
+
const DEBOUNCE_MS = 250;
|
|
2966
|
+
var HmrPlugin = class extends TypedEventEmitter {
|
|
2967
|
+
config;
|
|
2968
|
+
logger;
|
|
2969
|
+
lastUpdate = /* @__PURE__ */ new Map();
|
|
2970
|
+
awaitingCreateEcho = /* @__PURE__ */ new Set();
|
|
2971
|
+
server = null;
|
|
2972
|
+
dynamicRestartPatterns = /* @__PURE__ */ new Set();
|
|
2973
|
+
get hot() {
|
|
2974
|
+
return this.server?.environments.ssr.hot;
|
|
2975
|
+
}
|
|
2976
|
+
get dev() {
|
|
2977
|
+
return this.hot ? wrapHot(this.hot) : void 0;
|
|
2978
|
+
}
|
|
2979
|
+
constructor(config) {
|
|
2980
|
+
super();
|
|
2981
|
+
this.config = config;
|
|
2982
|
+
this.logger = new Logger("HMR", { channel: "hmr" });
|
|
2983
|
+
}
|
|
2984
|
+
get plugin() {
|
|
2985
|
+
return {
|
|
2986
|
+
name: "seedcord:hmr",
|
|
2987
|
+
configureServer: this.configureServer.bind(this),
|
|
2988
|
+
hotUpdate: this.hotUpdate.bind(this)
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
sendRefreshCommands(shouldRefresh) {
|
|
2992
|
+
this.dev?.send("seedcord:refresh-commands", { shouldRefresh });
|
|
2993
|
+
}
|
|
2994
|
+
configureServer(server) {
|
|
2995
|
+
this.server = server;
|
|
2996
|
+
server.watcher.on("add", (file) => this.handleFileEvent(file, "create"));
|
|
2997
|
+
server.watcher.on("unlink", (file) => this.handleFileEvent(file, "delete"));
|
|
2998
|
+
server.watcher.on("addDir", (file) => this.handleFileEvent(file, "createDir"));
|
|
2999
|
+
server.watcher.on("unlinkDir", (file) => this.handleFileEvent(file, "deleteDir"));
|
|
3000
|
+
server.watcher.add(this.config.configFile);
|
|
3001
|
+
server.watcher.on("change", (file) => this.handleChange(file));
|
|
3002
|
+
this.dev?.on("seedcord:commands-update-prompt", (data) => {
|
|
3003
|
+
this.emit("event", {
|
|
3004
|
+
type: "command-update-prompt",
|
|
3005
|
+
files: data.files
|
|
3006
|
+
});
|
|
3007
|
+
});
|
|
3008
|
+
this.dev?.on("seedcord:server-listening", (data) => {
|
|
3009
|
+
this.emit("event", {
|
|
3010
|
+
type: "server-listening",
|
|
3011
|
+
port: data.port
|
|
3012
|
+
});
|
|
3013
|
+
});
|
|
3014
|
+
this.dev?.on("seedcord:register-critical-files", (data) => {
|
|
3015
|
+
for (const pattern of data.patterns) this.dynamicRestartPatterns.add(pattern);
|
|
3016
|
+
this.logger.debug(`Registered ${String(data.patterns.length)} critical file patterns`);
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
isDebounced(file, type) {
|
|
3020
|
+
if (this.justSaw(file, type)) return true;
|
|
3021
|
+
this.lastUpdate.set(`${file}::${type}`, Date.now());
|
|
3022
|
+
return false;
|
|
3023
|
+
}
|
|
3024
|
+
justSaw(file, type) {
|
|
3025
|
+
const last = this.lastUpdate.get(`${file}::${type}`);
|
|
3026
|
+
return last !== void 0 && Date.now() - last < DEBOUNCE_MS;
|
|
3027
|
+
}
|
|
3028
|
+
handleChange(file) {
|
|
3029
|
+
if (!this.isCriticalFile(file) || this.isDebounced(file, "update")) return;
|
|
3030
|
+
this.reportRestartRequired(file);
|
|
3031
|
+
}
|
|
3032
|
+
reportRestartRequired(file) {
|
|
3033
|
+
const relPath = relative(process.cwd(), file);
|
|
3034
|
+
this.logger.warn(`${paint.coral("Critical file changed:")} ${paint.sky.bold(relPath)}. Restart required.`);
|
|
3035
|
+
this.emit("event", { type: "restart-required" });
|
|
3036
|
+
}
|
|
3037
|
+
handleFileEvent(file, type) {
|
|
3038
|
+
if (this.isDebounced(file, type)) return;
|
|
3039
|
+
if (type === "create") this.awaitingCreateEcho.add(file);
|
|
3040
|
+
const relPath = relative(process.cwd(), file);
|
|
3041
|
+
const typeColor = type === "create" || type === "createDir" ? paint.mint : type === "delete" || type === "deleteDir" ? paint.coral : paint.sky;
|
|
3042
|
+
this.logger.trace(`${typeColor(type.toUpperCase())} ${paint.mute(relPath)}`);
|
|
2694
3043
|
const payload = {
|
|
2695
3044
|
file,
|
|
2696
3045
|
type,
|
|
@@ -2701,20 +3050,19 @@ var HmrPlugin = class extends TypedEventEmitter {
|
|
|
2701
3050
|
hotUpdate(ctx) {
|
|
2702
3051
|
const { file, modules, server } = ctx;
|
|
2703
3052
|
const type = "update";
|
|
2704
|
-
if (this.isDebounced(file, type)) return [];
|
|
3053
|
+
if (this.awaitingCreateEcho.delete(file) && this.justSaw(file, "create") || this.isDebounced(file, type)) return [];
|
|
2705
3054
|
const relPath = relative(process.cwd(), file);
|
|
2706
|
-
this.logger.
|
|
3055
|
+
this.logger.trace(`${paint.sky(type.toUpperCase())} ${paint.mute(relPath)}`);
|
|
2707
3056
|
if (this.isCriticalFile(file)) {
|
|
2708
|
-
this.
|
|
2709
|
-
this.emit("event", { type: "restart-required" });
|
|
3057
|
+
this.reportRestartRequired(file);
|
|
2710
3058
|
return [];
|
|
2711
3059
|
}
|
|
2712
3060
|
const moduleGraph = server.moduleGraph;
|
|
2713
3061
|
const fileModules = moduleGraph.getModulesByFile(file);
|
|
2714
3062
|
const allModules = fileModules ? [...fileModules] : [];
|
|
2715
|
-
const combinedModules = new Set([...modules, ...allModules]);
|
|
3063
|
+
const combinedModules = /* @__PURE__ */ new Set([...modules, ...allModules]);
|
|
2716
3064
|
const affectedModules = this.getAffectedModules([...combinedModules]);
|
|
2717
|
-
const filesToInvalidate = new Set([file, ...affectedModules]);
|
|
3065
|
+
const filesToInvalidate = /* @__PURE__ */ new Set([file, ...affectedModules]);
|
|
2718
3066
|
for (const fileToInvalidate of filesToInvalidate) {
|
|
2719
3067
|
const mods = moduleGraph.getModulesByFile(fileToInvalidate);
|
|
2720
3068
|
if (mods) for (const mod of mods) moduleGraph.invalidateModule(mod);
|
|
@@ -2859,7 +3207,6 @@ var ViteDevRuntime = class {
|
|
|
2859
3207
|
if (!this.context || !this.viteServer || !this.moduleRunner) throw new SeedcordError(SeedcordErrorCode.CliStartFailed, [this.context?.config.instance ?? "runtime", "ViteDevRuntime.start() must complete before loadEntry()"]);
|
|
2860
3208
|
const { instance: entryPath } = this.context.config;
|
|
2861
3209
|
const projectRoot = this.context.config.root;
|
|
2862
|
-
const startTime = performance.now();
|
|
2863
3210
|
this.emit({
|
|
2864
3211
|
type: "module-loading",
|
|
2865
3212
|
path: entryPath
|
|
@@ -2867,15 +3214,11 @@ var ViteDevRuntime = class {
|
|
|
2867
3214
|
try {
|
|
2868
3215
|
const moduleId = toModuleId(projectRoot, entryPath);
|
|
2869
3216
|
const module = await this.moduleRunner.import(moduleId);
|
|
2870
|
-
const loadTime = performance.now() - startTime;
|
|
2871
3217
|
this.emit({
|
|
2872
3218
|
type: "module-loaded",
|
|
2873
3219
|
path: entryPath
|
|
2874
3220
|
});
|
|
2875
|
-
return {
|
|
2876
|
-
module,
|
|
2877
|
-
metadata: { loadTime }
|
|
2878
|
-
};
|
|
3221
|
+
return { module };
|
|
2879
3222
|
} catch (error) {
|
|
2880
3223
|
this.emit({
|
|
2881
3224
|
type: "module-error",
|
|
@@ -2906,191 +3249,376 @@ function toModuleId(projectRoot, file) {
|
|
|
2906
3249
|
}
|
|
2907
3250
|
|
|
2908
3251
|
//#endregion
|
|
2909
|
-
//#region src/commands/dev/
|
|
2910
|
-
const
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
3252
|
+
//#region src/commands/dev/tunnel/cloudflared.ts
|
|
3253
|
+
const BINARY = "cloudflared";
|
|
3254
|
+
const DOWNLOADS = "https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/";
|
|
3255
|
+
function systemLookup() {
|
|
3256
|
+
return {
|
|
3257
|
+
pathVar: process.env.PATH,
|
|
3258
|
+
pathExt: process.env.PATHEXT,
|
|
3259
|
+
platform: process.platform,
|
|
3260
|
+
delimiter,
|
|
3261
|
+
exists: existsSync
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
function findCloudflared(lookup) {
|
|
3265
|
+
const suffixes = lookup.platform === "win32" ? lookup.pathExt?.split(";") ?? [".EXE"] : [""];
|
|
3266
|
+
for (const dir of lookup.pathVar?.split(lookup.delimiter) ?? []) for (const suffix of suffixes) {
|
|
3267
|
+
const candidate = join(dir, `${BINARY}${suffix}`);
|
|
3268
|
+
if (lookup.exists(candidate)) return candidate;
|
|
2920
3269
|
}
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3270
|
+
}
|
|
3271
|
+
function installHint(platform) {
|
|
3272
|
+
if (platform === "darwin") return "brew install cloudflared";
|
|
3273
|
+
if (platform === "win32") return "winget install -e --id Cloudflare.cloudflared";
|
|
3274
|
+
return DOWNLOADS;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
//#endregion
|
|
3278
|
+
//#region src/commands/dev/tunnel/probe.ts
|
|
3279
|
+
const UNAUTHORIZED = 401;
|
|
3280
|
+
const POLL_INTERVAL_MS$1 = 250;
|
|
3281
|
+
const BUDGET_MS = 6e4;
|
|
3282
|
+
const PROBE_SECONDS = BUDGET_MS / 1e3;
|
|
3283
|
+
async function awaitReachable(url, deps, signal) {
|
|
3284
|
+
const budget = AbortSignal.any([signal, AbortSignal.timeout(BUDGET_MS)]);
|
|
3285
|
+
for (let attempt = 0; attempt < BUDGET_MS / POLL_INTERVAL_MS$1; attempt++) {
|
|
3286
|
+
try {
|
|
3287
|
+
if ((await deps.fetch(url, {
|
|
3288
|
+
method: "POST",
|
|
3289
|
+
signal: budget
|
|
3290
|
+
})).status === UNAUTHORIZED) return true;
|
|
3291
|
+
} catch {
|
|
3292
|
+
signal.throwIfAborted();
|
|
2927
3293
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
const lines = data.toString().split("\n");
|
|
2947
|
-
for (const line of lines) if (line.trim()) this.logger.info(line.trimEnd());
|
|
3294
|
+
await deps.wait(POLL_INTERVAL_MS$1);
|
|
3295
|
+
}
|
|
3296
|
+
return false;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
//#endregion
|
|
3300
|
+
//#region src/commands/dev/tunnel/CloudflaredTunnel.ts
|
|
3301
|
+
const POLL_INTERVAL_MS = 250;
|
|
3302
|
+
const HOSTNAME_ATTEMPTS = 240;
|
|
3303
|
+
const SETTLE_MS = 4e3;
|
|
3304
|
+
const GRACEFUL_EXIT_MS = 2e3;
|
|
3305
|
+
function freePort() {
|
|
3306
|
+
return new Promise((resolve, reject) => {
|
|
3307
|
+
const probe = createServer$1();
|
|
3308
|
+
probe.once("error", reject);
|
|
3309
|
+
probe.listen(0, "127.0.0.1", () => {
|
|
3310
|
+
const { port } = probe.address();
|
|
3311
|
+
probe.close(() => resolve(port));
|
|
2948
3312
|
});
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
function systemTunnelDeps() {
|
|
3316
|
+
return {
|
|
3317
|
+
spawn: (command, args) => spawn(command, args, { stdio: "ignore" }),
|
|
3318
|
+
fetch: (url, init) => fetch(url, init),
|
|
3319
|
+
freePort,
|
|
3320
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
var CloudflaredTunnel = class {
|
|
3324
|
+
deps;
|
|
3325
|
+
binary;
|
|
3326
|
+
onLost;
|
|
3327
|
+
child;
|
|
3328
|
+
failure;
|
|
3329
|
+
constructor(deps, binary, onLost) {
|
|
3330
|
+
this.deps = deps;
|
|
3331
|
+
this.binary = binary;
|
|
3332
|
+
this.onLost = onLost;
|
|
3333
|
+
}
|
|
3334
|
+
async open(signal, targetPort, onResolving) {
|
|
3335
|
+
const metricsPort = await this.deps.freePort();
|
|
3336
|
+
const child = this.deps.spawn(this.binary, [
|
|
3337
|
+
"tunnel",
|
|
3338
|
+
"--url",
|
|
3339
|
+
`http://localhost:${String(targetPort)}`,
|
|
3340
|
+
"--metrics",
|
|
3341
|
+
`127.0.0.1:${String(metricsPort)}`
|
|
3342
|
+
]);
|
|
3343
|
+
child.on("error", (error) => {
|
|
3344
|
+
this.failure = error;
|
|
2952
3345
|
});
|
|
2953
|
-
|
|
2954
|
-
this.
|
|
3346
|
+
child.on("exit", (code) => {
|
|
3347
|
+
if (this.child !== child) return;
|
|
3348
|
+
this.failure = /* @__PURE__ */ new Error(`cloudflared exited early with code ${String(code)}`);
|
|
2955
3349
|
});
|
|
2956
|
-
this.
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3350
|
+
this.child = child;
|
|
3351
|
+
const url = `https://${await this.readHostname(metricsPort, signal)}`;
|
|
3352
|
+
onResolving?.();
|
|
3353
|
+
await this.deps.wait(SETTLE_MS);
|
|
3354
|
+
await awaitReachable(url, this.deps, signal);
|
|
3355
|
+
child.once("exit", () => {
|
|
3356
|
+
if (this.child === child) this.onLost();
|
|
2960
3357
|
});
|
|
3358
|
+
return url;
|
|
2961
3359
|
}
|
|
2962
3360
|
stop() {
|
|
2963
|
-
const child = this.
|
|
3361
|
+
const child = this.child;
|
|
2964
3362
|
if (!child) return;
|
|
2965
|
-
this.
|
|
3363
|
+
this.child = void 0;
|
|
3364
|
+
this.failure = void 0;
|
|
2966
3365
|
child.kill("SIGTERM");
|
|
2967
|
-
const
|
|
2968
|
-
|
|
3366
|
+
const escalate = setTimeout(() => child.kill("SIGKILL"), GRACEFUL_EXIT_MS);
|
|
3367
|
+
escalate.unref();
|
|
2969
3368
|
child.once("exit", () => {
|
|
2970
|
-
clearTimeout(
|
|
3369
|
+
clearTimeout(escalate);
|
|
2971
3370
|
});
|
|
2972
3371
|
}
|
|
2973
|
-
|
|
2974
|
-
const
|
|
2975
|
-
|
|
3372
|
+
async readHostname(metricsPort, signal) {
|
|
3373
|
+
const budget = AbortSignal.any([signal, AbortSignal.timeout(6e4)]);
|
|
3374
|
+
for (let attempt = 0; attempt < HOSTNAME_ATTEMPTS && !this.failure; attempt++) {
|
|
3375
|
+
signal.throwIfAborted();
|
|
3376
|
+
if (budget.aborted) break;
|
|
3377
|
+
const hostname = await this.pollOnce(metricsPort, budget);
|
|
3378
|
+
if (hostname) return hostname;
|
|
3379
|
+
await this.deps.wait(POLL_INTERVAL_MS);
|
|
3380
|
+
}
|
|
3381
|
+
const cause = this.failure;
|
|
3382
|
+
this.stop();
|
|
3383
|
+
throw new SeedcordError(SeedcordErrorCode.CliTunnelUrlUnavailable, [6e4 / 1e3], { cause });
|
|
3384
|
+
}
|
|
3385
|
+
async pollOnce(metricsPort, signal) {
|
|
2976
3386
|
try {
|
|
2977
|
-
|
|
3387
|
+
const { hostname } = await (await this.deps.fetch(`http://127.0.0.1:${String(metricsPort)}/quicktunnel`, { signal })).json();
|
|
3388
|
+
return hostname === "" ? void 0 : hostname;
|
|
2978
3389
|
} catch {
|
|
2979
|
-
return
|
|
3390
|
+
return;
|
|
2980
3391
|
}
|
|
2981
3392
|
}
|
|
2982
3393
|
};
|
|
2983
3394
|
|
|
2984
3395
|
//#endregion
|
|
2985
|
-
//#region src/commands/dev/
|
|
2986
|
-
|
|
2987
|
-
|
|
3396
|
+
//#region src/commands/dev/tunnel/InteractionsEndpoint.ts
|
|
3397
|
+
const FIELD = "interactions_endpoint_url";
|
|
3398
|
+
function unverified(error) {
|
|
3399
|
+
if (!(error instanceof DiscordAPIError) || error.code !== RESTJSONErrorCodes.InvalidFormBodyOrContentType) return false;
|
|
3400
|
+
const { rawError } = error;
|
|
3401
|
+
return "errors" in rawError && containsField(rawError.errors);
|
|
2988
3402
|
}
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
tscRunner;
|
|
2998
|
-
stopPromise;
|
|
2999
|
-
constructor(config, runtime, store) {
|
|
3000
|
-
this.config = config;
|
|
3001
|
-
this.runtime = runtime;
|
|
3002
|
-
this.store = store;
|
|
3403
|
+
function containsField(errors) {
|
|
3404
|
+
return typeof errors === "object" && errors !== null && FIELD in errors;
|
|
3405
|
+
}
|
|
3406
|
+
var InteractionsEndpoint = class InteractionsEndpoint {
|
|
3407
|
+
makeRest;
|
|
3408
|
+
client;
|
|
3409
|
+
constructor(makeRest) {
|
|
3410
|
+
this.makeRest = makeRest;
|
|
3003
3411
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
this.store.apply(event);
|
|
3009
|
-
}
|
|
3412
|
+
static create(token) {
|
|
3413
|
+
const rest = new REST({
|
|
3414
|
+
version: "10",
|
|
3415
|
+
rejectOnRateLimit: ["/applications"]
|
|
3010
3416
|
});
|
|
3011
|
-
|
|
3417
|
+
return new InteractionsEndpoint(() => rest.setToken(token()));
|
|
3418
|
+
}
|
|
3419
|
+
async set(url, signal) {
|
|
3420
|
+
signal.throwIfAborted();
|
|
3421
|
+
if ((await this.rest().get(Routes.currentApplication())).interactions_endpoint_url === url) return;
|
|
3422
|
+
signal.throwIfAborted();
|
|
3012
3423
|
try {
|
|
3013
|
-
|
|
3014
|
-
return module;
|
|
3424
|
+
await this.write(url);
|
|
3015
3425
|
} catch (error) {
|
|
3016
|
-
|
|
3017
|
-
throw new SeedcordError(SeedcordErrorCode.
|
|
3426
|
+
if (!unverified(error)) throw error;
|
|
3427
|
+
throw new SeedcordError(SeedcordErrorCode.CliTunnelNotVerified, [url]);
|
|
3018
3428
|
}
|
|
3019
3429
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
this.
|
|
3430
|
+
rest() {
|
|
3431
|
+
this.client ??= this.makeRest();
|
|
3432
|
+
return this.client;
|
|
3433
|
+
}
|
|
3434
|
+
async clear() {
|
|
3435
|
+
await this.write(null);
|
|
3436
|
+
}
|
|
3437
|
+
async write(endpoint) {
|
|
3438
|
+
await this.rest().patch(Routes.currentApplication(), { body: { interactions_endpoint_url: endpoint } });
|
|
3439
|
+
}
|
|
3440
|
+
};
|
|
3441
|
+
|
|
3442
|
+
//#endregion
|
|
3443
|
+
//#region src/commands/dev/tunnel/TunnelCoordinator.ts
|
|
3444
|
+
const PASTE_HINT = "The tunnel is still up, so try pasting that URL into the dashboard.";
|
|
3445
|
+
const RESTART_HINT = "Restart for a fresh hostname, or set `tunnel` to an https URL you already serve.";
|
|
3446
|
+
function superseded(attempt) {
|
|
3447
|
+
return attempt.signal.aborted;
|
|
3448
|
+
}
|
|
3449
|
+
function opening(kind) {
|
|
3450
|
+
return kind === "quick" ? "Requesting a cloudflared tunnel to port" : "Checking if your tunnel reaches port";
|
|
3451
|
+
}
|
|
3452
|
+
var TunnelCoordinator = class {
|
|
3453
|
+
deps;
|
|
3454
|
+
target;
|
|
3455
|
+
current;
|
|
3456
|
+
constructor(deps) {
|
|
3457
|
+
this.deps = deps;
|
|
3458
|
+
}
|
|
3459
|
+
async onPort(port) {
|
|
3460
|
+
if (this.target === port) return;
|
|
3461
|
+
this.target = port;
|
|
3462
|
+
const attempt = this.begin();
|
|
3463
|
+
const startedAt = Date.now();
|
|
3464
|
+
const since = () => paint.mute(`+${formatUptime(Date.now() - startedAt)}`);
|
|
3465
|
+
const tunnel = this.deps.makeTunnel(() => this.reportLost(attempt));
|
|
3466
|
+
attempt.signal.addEventListener("abort", () => tunnel.stop(), { once: true });
|
|
3467
|
+
let patched = false;
|
|
3029
3468
|
try {
|
|
3030
|
-
this.
|
|
3031
|
-
this.
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
this.
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
});
|
|
3469
|
+
this.deps.onStatus("opening");
|
|
3470
|
+
this.deps.logger.info(`${opening(this.deps.kind)} ${paint.iris(String(port))}`);
|
|
3471
|
+
const url = await tunnel.open(attempt.signal, port, () => this.deps.onStatus("resolving"));
|
|
3472
|
+
if (superseded(attempt)) return;
|
|
3473
|
+
this.deps.logger.info(`Reachable at ${paint.sky.italic(url)} ${since()}`);
|
|
3474
|
+
this.deps.onStatus("registering");
|
|
3475
|
+
patched = true;
|
|
3476
|
+
await this.deps.endpoint.set(url, attempt.signal);
|
|
3477
|
+
if (superseded(attempt)) return;
|
|
3478
|
+
this.deps.onStatus("live");
|
|
3479
|
+
this.deps.logger.info(`Interactions endpoint set on the dev dashboard ${since()}`);
|
|
3041
3480
|
} catch (error) {
|
|
3042
|
-
|
|
3043
|
-
|
|
3481
|
+
if (superseded(attempt)) return;
|
|
3482
|
+
if (patched) attempt.abort();
|
|
3483
|
+
this.reportFailure(error, patched);
|
|
3044
3484
|
}
|
|
3045
3485
|
}
|
|
3486
|
+
reportLost(attempt) {
|
|
3487
|
+
if (superseded(attempt)) return;
|
|
3488
|
+
this.target = void 0;
|
|
3489
|
+
this.deps.onStatus("lost");
|
|
3490
|
+
this.deps.logger.error("cloudflared exited, so the bot has no public endpoint");
|
|
3491
|
+
}
|
|
3492
|
+
reportFailure(error, patched) {
|
|
3493
|
+
this.target = void 0;
|
|
3494
|
+
this.deps.onStatus(null);
|
|
3495
|
+
this.deps.logger.error("Tunnel setup failed, the bot runs without a public endpoint", error);
|
|
3496
|
+
if (this.deps.kind === "quick") this.deps.logger.info(patched ? RESTART_HINT : PASTE_HINT);
|
|
3497
|
+
}
|
|
3046
3498
|
async stop() {
|
|
3047
|
-
|
|
3048
|
-
|
|
3499
|
+
if (!this.current) return;
|
|
3500
|
+
this.target = void 0;
|
|
3501
|
+
this.current.abort();
|
|
3502
|
+
this.current = void 0;
|
|
3503
|
+
this.deps.onStatus(null);
|
|
3504
|
+
if (this.deps.kind === "configured") return;
|
|
3505
|
+
try {
|
|
3506
|
+
await this.deps.endpoint.clear();
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
this.deps.logger.warn("Could not clear the interactions endpoint", error);
|
|
3509
|
+
}
|
|
3049
3510
|
}
|
|
3050
|
-
|
|
3051
|
-
this.
|
|
3052
|
-
this.
|
|
3053
|
-
this.
|
|
3054
|
-
if (this.startupPromise) try {
|
|
3055
|
-
await this.startupPromise;
|
|
3056
|
-
} catch {}
|
|
3057
|
-
await this.instance?.shutdown.run(0, false);
|
|
3058
|
-
this.stopResolve?.();
|
|
3511
|
+
begin() {
|
|
3512
|
+
this.current?.abort();
|
|
3513
|
+
this.current = new AbortController();
|
|
3514
|
+
return this.current;
|
|
3059
3515
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3516
|
+
};
|
|
3517
|
+
|
|
3518
|
+
//#endregion
|
|
3519
|
+
//#region src/commands/dev/tunnel/createTunnelCoordinator.ts
|
|
3520
|
+
function missingCloudflaredHint(platform) {
|
|
3521
|
+
const arrow = paint.mute("→");
|
|
3522
|
+
return [
|
|
3523
|
+
`${paint.sky.bold("cloudflared")} is missing, so a tunnel can't be set up for you. Discord won't have any url to reach this bot`,
|
|
3524
|
+
`${arrow} install it with ${paint.mint.bold(installHint(platform))}`,
|
|
3525
|
+
`${arrow} or set ${paint.amber("tunnel")} to an https url you already serve, like ${paint.sky.italic("https://bot.example.com")}`
|
|
3526
|
+
].join("\n");
|
|
3527
|
+
}
|
|
3528
|
+
function createTunnelCoordinator(logger, onStatus, tunnel, findBinary = () => findCloudflared(systemLookup())) {
|
|
3529
|
+
if (tunnel.mode === "off") return void 0;
|
|
3530
|
+
const deps = systemTunnelDeps();
|
|
3531
|
+
const endpoint = InteractionsEndpoint.create(() => validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN")));
|
|
3532
|
+
if (tunnel.mode === "url") return new TunnelCoordinator({
|
|
3533
|
+
makeTunnel: () => ({
|
|
3534
|
+
open: async (signal, _port, onResolving) => {
|
|
3535
|
+
onResolving?.();
|
|
3536
|
+
if (await awaitReachable(tunnel.url, deps, signal)) return tunnel.url;
|
|
3537
|
+
throw new SeedcordError(SeedcordErrorCode.CliTunnelUnreachable, [tunnel.url, 60]);
|
|
3538
|
+
},
|
|
3539
|
+
stop: () => void 0
|
|
3540
|
+
}),
|
|
3541
|
+
kind: "configured",
|
|
3542
|
+
endpoint,
|
|
3543
|
+
onStatus,
|
|
3544
|
+
logger
|
|
3545
|
+
});
|
|
3546
|
+
const binary = findBinary();
|
|
3547
|
+
if (!binary) return void 0;
|
|
3548
|
+
return new TunnelCoordinator({
|
|
3549
|
+
makeTunnel: (onLost) => new CloudflaredTunnel(deps, binary, onLost),
|
|
3550
|
+
kind: "quick",
|
|
3551
|
+
endpoint,
|
|
3552
|
+
onStatus,
|
|
3553
|
+
logger
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
//#endregion
|
|
3558
|
+
//#region src/commands/dev/tunnel/TunnelRouter.ts
|
|
3559
|
+
function same(a, b) {
|
|
3560
|
+
if (a?.mode !== b.mode) return false;
|
|
3561
|
+
return a.mode === "url" && b.mode === "url" ? a.url === b.url : true;
|
|
3562
|
+
}
|
|
3563
|
+
var TunnelRouter = class {
|
|
3564
|
+
make;
|
|
3565
|
+
logger;
|
|
3566
|
+
coordinator;
|
|
3567
|
+
built;
|
|
3568
|
+
constructor(make, logger) {
|
|
3569
|
+
this.make = make;
|
|
3570
|
+
this.logger = logger;
|
|
3063
3571
|
}
|
|
3064
|
-
|
|
3065
|
-
|
|
3572
|
+
route(tunnel, event) {
|
|
3573
|
+
if (event.type !== "server-listening") return Promise.resolve();
|
|
3574
|
+
return this.routeTo(tunnel, event.port).catch((error) => {
|
|
3575
|
+
this.logger.error("Tunnel setup failed, the bot runs without a public endpoint", error);
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
async routeTo(tunnel, port) {
|
|
3579
|
+
if (!same(this.built, tunnel)) {
|
|
3580
|
+
await this.stop();
|
|
3581
|
+
this.built = tunnel;
|
|
3582
|
+
this.coordinator = tunnel.mode === "off" ? void 0 : this.make(tunnel);
|
|
3583
|
+
if (tunnel.mode !== "off" && !this.coordinator) this.logger.warn(missingCloudflaredHint(process.platform));
|
|
3584
|
+
}
|
|
3585
|
+
await this.coordinator?.onPort(port);
|
|
3586
|
+
}
|
|
3587
|
+
stop() {
|
|
3588
|
+
const running = this.coordinator;
|
|
3589
|
+
this.coordinator = void 0;
|
|
3590
|
+
return running?.stop() ?? Promise.resolve();
|
|
3066
3591
|
}
|
|
3067
3592
|
};
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3593
|
+
|
|
3594
|
+
//#endregion
|
|
3595
|
+
//#region src/commands/dev/DevRunner.ts
|
|
3596
|
+
const TUNNEL_TEARDOWN_MS = 3e3;
|
|
3071
3597
|
var DevRunner = class DevRunner {
|
|
3072
|
-
|
|
3073
|
-
configLoader;
|
|
3074
|
-
store;
|
|
3075
|
-
codegen;
|
|
3076
|
-
codegenLogger;
|
|
3598
|
+
deps;
|
|
3077
3599
|
currentSession = null;
|
|
3078
3600
|
signalResolve;
|
|
3079
3601
|
shouldQuit = false;
|
|
3602
|
+
quitting;
|
|
3080
3603
|
isDisconnected = false;
|
|
3081
3604
|
isRunning = false;
|
|
3082
3605
|
isRegenerating = false;
|
|
3083
|
-
constructor(
|
|
3084
|
-
this.
|
|
3085
|
-
this.configLoader = configLoader;
|
|
3086
|
-
this.store = store;
|
|
3087
|
-
this.codegen = codegen;
|
|
3088
|
-
this.codegenLogger = codegenLogger;
|
|
3606
|
+
constructor(deps) {
|
|
3607
|
+
this.deps = deps;
|
|
3089
3608
|
}
|
|
3090
3609
|
static create(logger, store) {
|
|
3091
3610
|
const moduleLoader = new RuntimeModuleLoader();
|
|
3092
3611
|
const codegenLogger = new Logger("Codegen", { channel: "cli" });
|
|
3093
|
-
|
|
3612
|
+
const tunnelLogger = new Logger("Tunnel", { channel: "cli" });
|
|
3613
|
+
const makeCoordinator = (tunnel) => createTunnelCoordinator(tunnelLogger, (status) => store.setTunnel(status), tunnel);
|
|
3614
|
+
return new DevRunner({
|
|
3615
|
+
locator: new ConfigLocator(logger),
|
|
3616
|
+
configLoader: new ConfigLoader(moduleLoader, logger),
|
|
3617
|
+
store,
|
|
3618
|
+
codegen: CodegenRunner.create(codegenLogger),
|
|
3619
|
+
codegenLogger,
|
|
3620
|
+
tunnel: new TunnelRouter(makeCoordinator, tunnelLogger)
|
|
3621
|
+
});
|
|
3094
3622
|
}
|
|
3095
3623
|
async run() {
|
|
3096
3624
|
if (this.isRunning) return;
|
|
@@ -3108,45 +3636,53 @@ var DevRunner = class DevRunner {
|
|
|
3108
3636
|
}
|
|
3109
3637
|
} finally {
|
|
3110
3638
|
this.isRunning = false;
|
|
3639
|
+
await this.quitting;
|
|
3111
3640
|
}
|
|
3112
3641
|
}
|
|
3113
3642
|
async handleDisconnected() {
|
|
3114
|
-
this.store.setPhase("disconnected");
|
|
3115
|
-
this.store.setStatus("Disconnected. Press r to restart.");
|
|
3116
|
-
this.store.setBusy(false);
|
|
3643
|
+
this.deps.store.setPhase("disconnected");
|
|
3644
|
+
this.deps.store.setStatus("Disconnected. Press r to restart.");
|
|
3645
|
+
this.deps.store.setBusy(false);
|
|
3117
3646
|
await this.waitForSignal();
|
|
3118
|
-
this.store.setBusy(true);
|
|
3647
|
+
this.deps.store.setBusy(true);
|
|
3119
3648
|
if (!this.shouldQuit) this.isDisconnected = false;
|
|
3120
3649
|
}
|
|
3121
3650
|
async runSession() {
|
|
3122
3651
|
resetChannelColors();
|
|
3123
|
-
this.store.setPhase("starting");
|
|
3124
|
-
this.store.setBusy(true);
|
|
3652
|
+
this.deps.store.setPhase("starting");
|
|
3653
|
+
this.deps.store.setBusy(true);
|
|
3125
3654
|
const config = await this.loadConfig();
|
|
3126
3655
|
const runtime = new ViteDevRuntime();
|
|
3127
|
-
this.currentSession = new
|
|
3656
|
+
this.currentSession = new DevSession(config, runtime, this.deps.store, (event) => {
|
|
3657
|
+
this.deps.tunnel.route(config.tunnel, event);
|
|
3658
|
+
});
|
|
3128
3659
|
try {
|
|
3129
3660
|
await this.currentSession.start(() => {
|
|
3130
|
-
this.store.setBusy(false);
|
|
3661
|
+
this.deps.store.setBusy(false);
|
|
3131
3662
|
});
|
|
3132
3663
|
} finally {
|
|
3133
|
-
this.store.setBusy(true);
|
|
3664
|
+
this.deps.store.setBusy(true);
|
|
3134
3665
|
await this.currentSession.dispose();
|
|
3135
3666
|
this.currentSession = null;
|
|
3136
3667
|
}
|
|
3137
3668
|
}
|
|
3138
3669
|
async handleError(error) {
|
|
3139
|
-
this.store.setPhase("error");
|
|
3140
|
-
this.store.setError(Error.isError(error) ? error : new Error(String(error)));
|
|
3141
|
-
this.store.setStatus("Press r to restart.");
|
|
3142
|
-
this.store.setBusy(false);
|
|
3670
|
+
this.deps.store.setPhase("error");
|
|
3671
|
+
this.deps.store.setError(Error.isError(error) ? error : new Error(String(error)));
|
|
3672
|
+
this.deps.store.setStatus("Press r to restart.");
|
|
3673
|
+
this.deps.store.setBusy(false);
|
|
3143
3674
|
await this.waitForSignal();
|
|
3144
|
-
this.store.setBusy(true);
|
|
3675
|
+
this.deps.store.setBusy(true);
|
|
3145
3676
|
}
|
|
3146
3677
|
async quit() {
|
|
3678
|
+
this.quitting ??= this.runQuit();
|
|
3679
|
+
return this.quitting;
|
|
3680
|
+
}
|
|
3681
|
+
async runQuit() {
|
|
3147
3682
|
this.shouldQuit = true;
|
|
3148
|
-
this.store.setPhase("quitting");
|
|
3683
|
+
this.deps.store.setPhase("quitting");
|
|
3149
3684
|
await this.currentSession?.stop();
|
|
3685
|
+
await settleWithin(this.deps.tunnel.stop(), TUNNEL_TEARDOWN_MS);
|
|
3150
3686
|
this.signalResolve?.();
|
|
3151
3687
|
}
|
|
3152
3688
|
async restart() {
|
|
@@ -3168,9 +3704,9 @@ var DevRunner = class DevRunner {
|
|
|
3168
3704
|
if (this.isRegenerating) return;
|
|
3169
3705
|
this.isRegenerating = true;
|
|
3170
3706
|
try {
|
|
3171
|
-
await this.codegen.run(false);
|
|
3707
|
+
await this.deps.codegen.run(false);
|
|
3172
3708
|
} catch (error) {
|
|
3173
|
-
this.codegenLogger.error("Command registry regeneration failed", error);
|
|
3709
|
+
this.deps.codegenLogger.error("Command registry regeneration failed", error);
|
|
3174
3710
|
} finally {
|
|
3175
3711
|
this.isRegenerating = false;
|
|
3176
3712
|
}
|
|
@@ -3181,30 +3717,28 @@ var DevRunner = class DevRunner {
|
|
|
3181
3717
|
});
|
|
3182
3718
|
}
|
|
3183
3719
|
async loadConfig() {
|
|
3184
|
-
const configPath = this.locator.locate();
|
|
3185
|
-
return this.configLoader.load(configPath);
|
|
3720
|
+
const configPath = this.deps.locator.locate();
|
|
3721
|
+
return this.deps.configLoader.load(configPath);
|
|
3186
3722
|
}
|
|
3187
3723
|
};
|
|
3188
3724
|
|
|
3189
3725
|
//#endregion
|
|
3190
3726
|
//#region src/commands/dev/DevCommand.ts
|
|
3191
3727
|
var DevCommand = class extends BaseCommand {
|
|
3192
|
-
runner;
|
|
3193
|
-
store;
|
|
3194
3728
|
constructor() {
|
|
3195
3729
|
super("dev", "Run a Seedcord instance from the config file", "Dev");
|
|
3196
|
-
this.store = new DevStore();
|
|
3197
|
-
this.runner = DevRunner.create(this.logger, this.store);
|
|
3198
3730
|
}
|
|
3199
3731
|
register(program) {
|
|
3200
3732
|
program.command(this.name).description(this.description).action(async () => {
|
|
3733
|
+
const store = new DevStore();
|
|
3734
|
+
const runner = DevRunner.create(this.logger, store);
|
|
3201
3735
|
const onSignal = () => {
|
|
3202
|
-
|
|
3736
|
+
runner.quit();
|
|
3203
3737
|
};
|
|
3204
3738
|
process.once("SIGINT", onSignal);
|
|
3205
3739
|
process.once("SIGTERM", onSignal);
|
|
3206
3740
|
try {
|
|
3207
|
-
await this.runDevApp();
|
|
3741
|
+
await this.runDevApp(store, runner);
|
|
3208
3742
|
} catch (error) {
|
|
3209
3743
|
this.logger.error("Seedcord dev failed", error);
|
|
3210
3744
|
process.exitCode = 1;
|
|
@@ -3219,18 +3753,21 @@ var DevCommand = class extends BaseCommand {
|
|
|
3219
3753
|
printLogLocation() {
|
|
3220
3754
|
process.stdout.write("seedcord dev stopped. logs: logs/\n");
|
|
3221
3755
|
}
|
|
3222
|
-
async runDevApp() {
|
|
3756
|
+
async runDevApp(store, runner) {
|
|
3757
|
+
let failure;
|
|
3223
3758
|
let runResult = Promise.resolve();
|
|
3224
3759
|
const { unmount, waitUntilExit } = render(React.createElement(DevApp, {
|
|
3225
|
-
store
|
|
3226
|
-
onQuit: () =>
|
|
3227
|
-
onDisconnect: () =>
|
|
3228
|
-
onRestart: () =>
|
|
3229
|
-
onRefreshCommands: (shouldRefresh) =>
|
|
3760
|
+
store,
|
|
3761
|
+
onQuit: () => runner.quit(),
|
|
3762
|
+
onDisconnect: () => runner.disconnect(),
|
|
3763
|
+
onRestart: () => runner.restart(),
|
|
3764
|
+
onRefreshCommands: (shouldRefresh) => runner.refreshCommands(shouldRefresh),
|
|
3230
3765
|
onReady: () => {
|
|
3231
|
-
runResult =
|
|
3766
|
+
runResult = runner.run().finally(async () => {
|
|
3232
3767
|
await LogStore.instance.flush();
|
|
3233
3768
|
unmount();
|
|
3769
|
+
}).catch((error) => {
|
|
3770
|
+
failure = { error };
|
|
3234
3771
|
});
|
|
3235
3772
|
}
|
|
3236
3773
|
}), {
|
|
@@ -3239,6 +3776,7 @@ var DevCommand = class extends BaseCommand {
|
|
|
3239
3776
|
});
|
|
3240
3777
|
await waitUntilExit();
|
|
3241
3778
|
await runResult;
|
|
3779
|
+
if (failure) throw failure.error;
|
|
3242
3780
|
}
|
|
3243
3781
|
};
|
|
3244
3782
|
|