@serverkgg/bridge 0.1.11 → 0.1.13
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/package.json +1 -1
- package/rules/package.md +2 -1
- package/src/bot/botDeploy.test.ts +57 -0
- package/src/bot/botEnvironment.test.ts +21 -0
- package/src/bot/botEnvironment.ts +10 -0
- package/src/bot/botRun.test.ts +296 -0
- package/src/bot/botRun.ts +109 -0
- package/src/bot/botState.test.ts +34 -1
- package/src/bot/botState.ts +1 -0
- package/src/bot/botVariables.test.ts +22 -0
- package/src/bot/botVariables.ts +3 -0
- package/src/bot/detect/detect.test.ts +172 -0
- package/src/bot/detect/detect.ts +31 -0
- package/src/bot/detect/detectResult.ts +1 -0
- package/src/bot/detect/detectTable.test.ts +25 -2
- package/src/bot/detect/detectTable.ts +24 -1
- package/src/bot/detect/keys.test.ts +7 -0
- package/src/bot/detect/keys.ts +3 -0
- package/src/bot/detect/serverkJson.test.ts +21 -0
- package/src/bot/detect/serverkJson.ts +13 -1
- package/src/bot/index.ts +1 -0
- package/src/cli/link.ts +3 -3
- package/src/cli/main.ts +8 -0
- package/src/cli/readme.test.ts +470 -0
- package/src/cli/readme.ts +570 -0
- package/src/cli/validate.test.ts +41 -1
- package/src/cli/validate.ts +25 -0
- package/src/context/bridgeContext.ts +1 -0
- package/src/modules/bridgeModule.ts +1 -0
- package/src/protocol/bridgeManifest.ts +1 -0
- package/src/runtime/bridgeContext.test.ts +6 -0
- package/src/runtime/bridgeContext.ts +7 -2
- package/src/runtime/bridgeExec.test.ts +150 -0
- package/src/runtime/bridgeExec.ts +67 -19
- package/src/runtime/bridgeLineSpacing.test.ts +132 -0
- package/src/runtime/bridgeLineSpacing.ts +49 -0
- package/src/runtime/bridgeLog.ts +9 -0
- package/src/runtime/bridgeRedaction.test.ts +28 -2
- package/src/runtime/bridgeRedaction.ts +7 -1
- package/src/runtime/bridgeRegistry.test.ts +30 -0
- package/src/runtime/bridgeRegistry.ts +1 -0
- package/src/runtime/bridgeScopedContext.ts +3 -0
- package/src/runtime/bridgeSetupHandlers.test.ts +3 -0
- package/src/runtime/bridgeSupervisor.ts +7 -9
- package/src/runtime/serve.ts +9 -2
- package/src/steam/steamcmdNarrator.test.ts +18 -5
- package/src/steam/steamcmdNarrator.ts +26 -2
- package/src/wine/protonCommand.test.ts +17 -1
- package/src/wine/protonCommand.ts +7 -1
- package/src/wire/panel.test.ts +32 -0
- package/src/wire/panel.ts +1 -0
|
@@ -4,8 +4,9 @@ import { BridgeLifecycleSubject } from "../modules/bridgeLifecycleSubject";
|
|
|
4
4
|
import { BRIDGE_STOP_SIGNALS, type BridgeStopSignal } from "../modules/bridgeStopSignal";
|
|
5
5
|
import { BRIDGE_CRASH_LINE_LIMIT, BridgeCrashPhase, type BridgeCrashRecord } from "../protocol";
|
|
6
6
|
import { stripAnsi } from "../utils";
|
|
7
|
+
import { createLineSpacing } from "./bridgeLineSpacing";
|
|
7
8
|
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
8
|
-
import { errorText } from "./bridgeLog";
|
|
9
|
+
import { errorText, print } from "./bridgeLog";
|
|
9
10
|
import { settleUid, unprivilegedArgv } from "./bridgePrivilege";
|
|
10
11
|
import { readProcessGroup, signalGroup } from "./bridgeSignal";
|
|
11
12
|
|
|
@@ -286,11 +287,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
286
287
|
}
|
|
287
288
|
: raw;
|
|
288
289
|
|
|
289
|
-
|
|
290
|
-
process.stderr.write(`${entry.line}\n`);
|
|
291
|
-
} else {
|
|
292
|
-
process.stdout.write(`${entry.line}\n`);
|
|
293
|
-
}
|
|
290
|
+
print(entry.line, entry.stream);
|
|
294
291
|
|
|
295
292
|
tail.push(entry);
|
|
296
293
|
|
|
@@ -306,6 +303,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
306
303
|
const pump = async (stream: ReadableStream<Uint8Array>, kind: BridgeStream) => {
|
|
307
304
|
const decoder = new TextDecoder();
|
|
308
305
|
const reader = stream.getReader();
|
|
306
|
+
const spacing = createLineSpacing();
|
|
309
307
|
|
|
310
308
|
let buffer = "";
|
|
311
309
|
|
|
@@ -325,7 +323,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
325
323
|
|
|
326
324
|
buffer = split.rest;
|
|
327
325
|
|
|
328
|
-
for (const line of split.lines) {
|
|
326
|
+
for (const line of split.lines.flatMap(spacing)) {
|
|
329
327
|
emit({
|
|
330
328
|
stream: kind,
|
|
331
329
|
line,
|
|
@@ -333,10 +331,10 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
333
331
|
}
|
|
334
332
|
}
|
|
335
333
|
|
|
336
|
-
|
|
334
|
+
for (const line of spacing(buffer)) {
|
|
337
335
|
emit({
|
|
338
336
|
stream: kind,
|
|
339
|
-
line
|
|
337
|
+
line,
|
|
340
338
|
});
|
|
341
339
|
}
|
|
342
340
|
};
|
package/src/runtime/serve.ts
CHANGED
|
@@ -19,9 +19,15 @@ import { validateDriver } from "./bridgeDriver";
|
|
|
19
19
|
import { createEventRing } from "./bridgeEvents";
|
|
20
20
|
import { decideInstall, recountHold } from "./bridgeInstall";
|
|
21
21
|
import { createBridgeLiveConfig } from "./bridgeLiveConfig";
|
|
22
|
-
import { errorDetail, errorText, log as stdoutLog } from "./bridgeLog";
|
|
22
|
+
import { errorDetail, errorText, print, log as stdoutLog } from "./bridgeLog";
|
|
23
23
|
import { pendingResult, rejectedPending, unsupportedPending } from "./bridgePending";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createBridgeRedaction,
|
|
26
|
+
redactLocalized,
|
|
27
|
+
redactLogger,
|
|
28
|
+
redactPrinter,
|
|
29
|
+
secretFieldKeys,
|
|
30
|
+
} from "./bridgeRedaction";
|
|
25
31
|
import { createRegistry } from "./bridgeRegistry";
|
|
26
32
|
import { createServer } from "./bridgeServer";
|
|
27
33
|
import { setupSummaryOf } from "./bridgeSetup";
|
|
@@ -182,6 +188,7 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
182
188
|
supervisor,
|
|
183
189
|
{
|
|
184
190
|
onLog: log,
|
|
191
|
+
onPrint: redactPrinter(print, redact),
|
|
185
192
|
onEmit(event, payload) {
|
|
186
193
|
ring.emit(event, payload);
|
|
187
194
|
},
|
|
@@ -20,7 +20,7 @@ const recorder = () => {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
describe("steamcmdNarrator", () => {
|
|
23
|
-
it("reports download progress in ten percent steps", () => {
|
|
23
|
+
it("reports download progress in ten percent steps, each line with the percent steamcmd printed", () => {
|
|
24
24
|
const { log, lines, fields } = recorder();
|
|
25
25
|
const narrate = steamcmdNarrator(log, "palworld");
|
|
26
26
|
|
|
@@ -36,10 +36,10 @@ describe("steamcmdNarrator", () => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
expect(lines).toEqual([
|
|
39
|
-
"downloading palworld",
|
|
40
|
-
"downloading palworld",
|
|
41
|
-
"downloading palworld",
|
|
42
|
-
"downloading palworld",
|
|
39
|
+
"downloading palworld 2%",
|
|
40
|
+
"downloading palworld 11%",
|
|
41
|
+
"downloading palworld 25%",
|
|
42
|
+
"downloading palworld 99%",
|
|
43
43
|
]);
|
|
44
44
|
expect(fields).toEqual([
|
|
45
45
|
{
|
|
@@ -74,6 +74,19 @@ describe("steamcmdNarrator", () => {
|
|
|
74
74
|
]);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
it("says how much of the download is done when steamcmd prints the byte counts", () => {
|
|
78
|
+
const { log, lines } = recorder();
|
|
79
|
+
const narrate = steamcmdNarrator(log, "palworld");
|
|
80
|
+
|
|
81
|
+
narrate("Update state (0x61) downloading, progress: 1.11 (127644881 / 11530459441)");
|
|
82
|
+
narrate("Update state (0x61) downloading, progress: 40.02 (4614180864 / 11530459441)");
|
|
83
|
+
|
|
84
|
+
expect(lines).toEqual([
|
|
85
|
+
"downloading palworld 1% (122 MB of 10.7 GB)",
|
|
86
|
+
"downloading palworld 40% (4.3 GB of 10.7 GB)",
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
77
90
|
it("passes notable lines through with the ansi stripped", () => {
|
|
78
91
|
const { log, lines } = recorder();
|
|
79
92
|
const narrate = steamcmdNarrator(log, "rust");
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import type { BridgeLog } from "../context";
|
|
2
2
|
import { stripAnsi } from "../utils";
|
|
3
3
|
|
|
4
|
-
const PROGRESS =
|
|
4
|
+
const PROGRESS =
|
|
5
|
+
/Update state \(0x[0-9a-f]+\)\s*(?<phase>[^,]+),\s*progress:\s*(?<percent>\d+)\.\d+(?:\s*\((?<done>\d+)\s*\/\s*(?<total>\d+)\))?/i;
|
|
6
|
+
|
|
7
|
+
const GIGABYTE = 1024 ** 3;
|
|
8
|
+
|
|
9
|
+
const MEGABYTE = 1024 ** 2;
|
|
5
10
|
|
|
6
11
|
const PROGRESS_STEP = 10;
|
|
7
12
|
|
|
8
13
|
const NOTABLE = /^(Error|Warning|Failed|Success|Logging in|Connecting|Update state \(0x\d+\) (?!downloading))/i;
|
|
9
14
|
|
|
15
|
+
const byteSizeOf = (bytes: number) => {
|
|
16
|
+
if (bytes >= GIGABYTE) {
|
|
17
|
+
return `${(bytes / GIGABYTE).toFixed(1)} GB`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `${Math.round(bytes / MEGABYTE)} MB`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const progressLineOf = (phase: string, label: string, percent: number, done: number, total: number) => {
|
|
24
|
+
if (total < MEGABYTE) {
|
|
25
|
+
return `${phase} ${label} ${percent}%`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${phase} ${label} ${percent}% (${byteSizeOf(done)} of ${byteSizeOf(total)})`;
|
|
29
|
+
};
|
|
30
|
+
|
|
10
31
|
export const steamcmdNarrator = (log: BridgeLog, label: string) => {
|
|
11
32
|
let phase = "";
|
|
12
33
|
let reported = -1;
|
|
@@ -28,7 +49,10 @@ export const steamcmdNarrator = (log: BridgeLog, label: string) => {
|
|
|
28
49
|
if (step > reported) {
|
|
29
50
|
reported = step;
|
|
30
51
|
|
|
31
|
-
|
|
52
|
+
const done = Number(progress.groups.done ?? 0);
|
|
53
|
+
const total = Number(progress.groups.total ?? 0);
|
|
54
|
+
|
|
55
|
+
log(progressLineOf(phase, label, percent, done, total), {
|
|
32
56
|
percent: step,
|
|
33
57
|
});
|
|
34
58
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import type { BridgeContext } from "../context";
|
|
3
|
-
import { protonCommand, protonCompatPath } from "./protonCommand";
|
|
3
|
+
import { ProtonVerb, protonCommand, protonCompatPath } from "./protonCommand";
|
|
4
4
|
|
|
5
5
|
const ensured: string[] = [];
|
|
6
6
|
|
|
@@ -41,6 +41,22 @@ describe("protonCommand", () => {
|
|
|
41
41
|
]);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
it("runs straight in the prefix when asked, so the game keeps the container's stdout", async () => {
|
|
45
|
+
expect(
|
|
46
|
+
(
|
|
47
|
+
await protonCommand(context, {
|
|
48
|
+
appId: "2394010",
|
|
49
|
+
exe: "Pal/Binaries/Win64/PalServer-Win64-Shipping-Cmd.exe",
|
|
50
|
+
verb: ProtonVerb.RunInPrefix,
|
|
51
|
+
})
|
|
52
|
+
).slice(-3),
|
|
53
|
+
).toEqual([
|
|
54
|
+
"/opt/proton/proton",
|
|
55
|
+
"runinprefix",
|
|
56
|
+
"Pal/Binaries/Win64/PalServer-Win64-Shipping-Cmd.exe",
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
|
|
44
60
|
it("ensures the compat and steam directories", async () => {
|
|
45
61
|
ensured.length = 0;
|
|
46
62
|
|
|
@@ -7,6 +7,11 @@ export const PROTON_COMPAT_DIRECTORY = ".proton";
|
|
|
7
7
|
|
|
8
8
|
export const STEAM_COMPAT_CLIENT_DIRECTORY = ".steam";
|
|
9
9
|
|
|
10
|
+
export enum ProtonVerb {
|
|
11
|
+
Run = "run",
|
|
12
|
+
RunInPrefix = "runinprefix",
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export interface ProtonCommandOptions {
|
|
11
16
|
appId: string;
|
|
12
17
|
exe: string;
|
|
@@ -16,6 +21,7 @@ export interface ProtonCommandOptions {
|
|
|
16
21
|
steamDir?: string;
|
|
17
22
|
home?: string;
|
|
18
23
|
environment?: Record<string, string>;
|
|
24
|
+
verb?: ProtonVerb;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export const protonCompatPath = (appId: string, compatDir: string = PROTON_COMPAT_DIRECTORY) => {
|
|
@@ -42,7 +48,7 @@ export const protonCommand = async (context: BridgeContext, options: ProtonComma
|
|
|
42
48
|
"env",
|
|
43
49
|
...Object.entries(environment).map(([key, value]) => `${key}=${value}`),
|
|
44
50
|
`${protonDir}/proton`,
|
|
45
|
-
|
|
51
|
+
options.verb ?? ProtonVerb.Run,
|
|
46
52
|
options.exe,
|
|
47
53
|
...(options.args ?? []),
|
|
48
54
|
];
|
package/src/wire/panel.test.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
BridgePlace,
|
|
14
14
|
type BridgeSetupManifest,
|
|
15
15
|
BridgeSetupStepKind,
|
|
16
|
+
type BridgeTerminalManifest,
|
|
16
17
|
BridgeUploadMode,
|
|
17
18
|
} from "@serverkgg/bridge/protocol";
|
|
18
19
|
import { bridgeAvailabilitySchema, bridgeCellsSchema, bridgeManifestSchema } from "./panel";
|
|
@@ -783,3 +784,34 @@ describe("the availability schema", () => {
|
|
|
783
784
|
expect(parsed.modules[0]?.actions).toContain(BridgeAction.Availability);
|
|
784
785
|
});
|
|
785
786
|
});
|
|
787
|
+
|
|
788
|
+
const terminalManifest = (shell: boolean): BridgeManifest => ({
|
|
789
|
+
version: "1.0.0",
|
|
790
|
+
panel: null,
|
|
791
|
+
terminal: {
|
|
792
|
+
commands: [],
|
|
793
|
+
rules: [],
|
|
794
|
+
shell,
|
|
795
|
+
},
|
|
796
|
+
setup: null,
|
|
797
|
+
modules: [],
|
|
798
|
+
events: [],
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
describe("the terminal mirror", () => {
|
|
802
|
+
it("carries the shell flag through unchanged", () => {
|
|
803
|
+
expect(bridgeManifestSchema.parse(terminalManifest(true)).terminal?.shell).toBe(true);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("defaults a terminal from an older bridge that carries no shell flag to false", () => {
|
|
807
|
+
const older = terminalManifest(true);
|
|
808
|
+
|
|
809
|
+
delete (older.terminal as Partial<BridgeTerminalManifest>).shell;
|
|
810
|
+
|
|
811
|
+
expect(bridgeManifestSchema.parse(older).terminal).toEqual({
|
|
812
|
+
commands: [],
|
|
813
|
+
rules: [],
|
|
814
|
+
shell: false,
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
});
|
package/src/wire/panel.ts
CHANGED
|
@@ -318,6 +318,7 @@ export const bridgeTerminalRuleSchema: z.ZodType<BridgeTerminalRuleManifest> = z
|
|
|
318
318
|
export const bridgeTerminalManifestSchema: z.ZodType<BridgeTerminalManifest> = z.object({
|
|
319
319
|
commands: z.array(bridgeTerminalCommandSchema).max(128),
|
|
320
320
|
rules: z.array(bridgeTerminalRuleSchema).max(64),
|
|
321
|
+
shell: z.boolean().default(false),
|
|
321
322
|
});
|
|
322
323
|
|
|
323
324
|
const setupStepBase = {
|