@serverkgg/bridge 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/src/cli/validate.ts +1 -0
  3. package/src/context/bridgeContext.ts +8 -1
  4. package/src/core/bridgeKind.ts +1 -0
  5. package/src/core/bridgeProbeError.ts +16 -0
  6. package/src/core/bridgeSetup.ts +20 -0
  7. package/src/core/bridgeValue.test.ts +29 -0
  8. package/src/core/bridgeValue.ts +12 -0
  9. package/src/core/index.ts +2 -0
  10. package/src/guides/blocks.ts +8 -0
  11. package/src/guides/markdown.test.ts +18 -0
  12. package/src/guides/markdown.ts +7 -1
  13. package/src/manifest/manifest.test.ts +49 -2
  14. package/src/manifest/presence.ts +9 -1
  15. package/src/modules/bridge.ts +20 -0
  16. package/src/modules/bridgeDriver.ts +2 -0
  17. package/src/modules/bridgeModule.ts +7 -0
  18. package/src/panel/bridgeDetail.ts +5 -5
  19. package/src/probes/a2s.test.ts +71 -0
  20. package/src/probes/a2s.ts +38 -11
  21. package/src/protocol/bridgeFrame.ts +5 -0
  22. package/src/protocol/bridgeLimits.ts +2 -0
  23. package/src/protocol/bridgeManifest.ts +2 -0
  24. package/src/protocol/bridgeSetup.ts +97 -0
  25. package/src/protocol/index.ts +1 -0
  26. package/src/runtime/bridgeConfigReader.ts +12 -8
  27. package/src/runtime/bridgeContext.ts +28 -2
  28. package/src/runtime/bridgeDriver.test.ts +272 -2
  29. package/src/runtime/bridgeDriver.ts +82 -1
  30. package/src/runtime/bridgeEvents.test.ts +96 -0
  31. package/src/runtime/bridgeEvents.ts +4 -1
  32. package/src/runtime/bridgeRegistry.test.ts +38 -1
  33. package/src/runtime/bridgeRegistry.ts +14 -1
  34. package/src/runtime/bridgeSecrets.test.ts +121 -0
  35. package/src/runtime/bridgeSecrets.ts +43 -0
  36. package/src/runtime/bridgeServer.test.ts +262 -2
  37. package/src/runtime/bridgeServer.ts +17 -0
  38. package/src/runtime/bridgeSetup.test.ts +189 -0
  39. package/src/runtime/bridgeSetup.ts +63 -0
  40. package/src/runtime/bridgeSignal.test.ts +114 -0
  41. package/src/runtime/bridgeSignal.ts +31 -0
  42. package/src/runtime/bridgeSupervisor.test.ts +111 -0
  43. package/src/runtime/bridgeSupervisor.ts +37 -5
  44. package/src/runtime/serve.ts +52 -1
  45. package/src/utils/index.ts +1 -0
  46. package/src/utils/stripAnsi.ts +12 -0
  47. package/src/wire/panel.test.ts +93 -0
  48. package/src/wire/panel.ts +40 -0
@@ -1,5 +1,5 @@
1
1
  import { chmod, chown, mkdir, rm } from "node:fs/promises";
2
- import { BridgeDesired, type BridgeInstallHold, BridgeRuntime, BridgeUserError } from "../core";
2
+ import { BridgeDesired, type BridgeInstallHold, BridgeRuntime, BridgeUserError, type LocalizedText } from "../core";
3
3
  import type { BridgeDriver } from "../modules";
4
4
  import {
5
5
  BRIDGE_INSTALL_ERROR_LIMIT,
@@ -7,6 +7,8 @@ import {
7
7
  type BridgeConfig,
8
8
  type BridgePendingResult,
9
9
  type BridgeSampleResult,
10
+ type BridgeSetupResult,
11
+ type BridgeSetupRuntime,
10
12
  type BridgeStatus,
11
13
  } from "../protocol";
12
14
  import { configPath, readConfig, socketPath, statePath } from "./bridgeConfigReader";
@@ -18,6 +20,7 @@ import { errorText, log } from "./bridgeLog";
18
20
  import { pendingResult, rejectedPending, unsupportedPending } from "./bridgePending";
19
21
  import { createRegistry } from "./bridgeRegistry";
20
22
  import { createServer } from "./bridgeServer";
23
+ import { failedSetup, setupSummaryOf, unsupportedSetup } from "./bridgeSetup";
21
24
  import { readDesired, writeDesired } from "./bridgeState";
22
25
  import { createSupervisor } from "./bridgeSupervisor";
23
26
 
@@ -67,8 +70,12 @@ export const serve = async (driver: BridgeDriver) => {
67
70
 
68
71
  let installError: string | null = null;
69
72
 
73
+ let installErrorText: LocalizedText | null = null;
74
+
70
75
  let installHold: BridgeInstallHold | null = null;
71
76
 
77
+ let setupRuntime: BridgeSetupRuntime | null = null;
78
+
72
79
  const supervisor = createSupervisor({
73
80
  cwd: config.cwd,
74
81
  uid: config.gameUid,
@@ -98,6 +105,9 @@ export const serve = async (driver: BridgeDriver) => {
98
105
  onEmit(event, payload) {
99
106
  ring.emit(event, payload);
100
107
  },
108
+ onSetup(runtime) {
109
+ setupRuntime = runtime;
110
+ },
101
111
  });
102
112
 
103
113
  const registry = createRegistry(driver, context, supervisor, ring);
@@ -113,7 +123,9 @@ export const serve = async (driver: BridgeDriver) => {
113
123
  outOfMemory: supervisor.outOfMemory,
114
124
  restarts: supervisor.restarts,
115
125
  installError,
126
+ installErrorText,
116
127
  installHold,
128
+ setup: setupSummaryOf(setupRuntime),
117
129
  };
118
130
  };
119
131
 
@@ -307,6 +319,44 @@ export const serve = async (driver: BridgeDriver) => {
307
319
 
308
320
  return await pending();
309
321
  },
322
+ onSetupStatus() {
323
+ if (!driver.setup) {
324
+ return unsupportedSetup();
325
+ }
326
+
327
+ return {
328
+ supported: true,
329
+ runtime: setupRuntime,
330
+ };
331
+ },
332
+ async onSetupSubmit(step, action, args): Promise<BridgeSetupResult> {
333
+ const module = driver.setup;
334
+
335
+ if (!module?.submit) {
336
+ return unsupportedSetup();
337
+ }
338
+
339
+ try {
340
+ await module.submit(context, step, action, args);
341
+ } catch (error) {
342
+ if (!(error instanceof BridgeUserError)) {
343
+ throw error;
344
+ }
345
+
346
+ log.warn("setup submit rejected", {
347
+ step,
348
+ action,
349
+ error: error.message,
350
+ });
351
+
352
+ return failedSetup(setupRuntime, error.text);
353
+ }
354
+
355
+ return {
356
+ supported: true,
357
+ runtime: setupRuntime,
358
+ };
359
+ },
310
360
  });
311
361
 
312
362
  await chmod(socketPath(config), SOCKET_MODE);
@@ -358,6 +408,7 @@ export const serve = async (driver: BridgeDriver) => {
358
408
  log("install complete");
359
409
  } catch (error) {
360
410
  installError = errorText(error).slice(0, BRIDGE_INSTALL_ERROR_LIMIT);
411
+ installErrorText = error instanceof BridgeUserError ? error.text : null;
361
412
 
362
413
  supervisor.setRuntime(BridgeRuntime.Crashed);
363
414
 
@@ -1 +1,2 @@
1
1
  export * from "./glob";
2
+ export * from "./stripAnsi";
@@ -0,0 +1,12 @@
1
+ const ANSI_PATTERN = new RegExp(
2
+ [
3
+ "\\u001B\\][^\\u0007\\u001B]*(?:\\u0007|\\u001B\\\\)",
4
+ "\\u001B\\[[0-9;?]*[ -/]*[@-~]",
5
+ "\\u001B[@-Z\\\\-_]",
6
+ ].join("|"),
7
+ "g",
8
+ );
9
+
10
+ export const stripAnsi = (line: string): string => {
11
+ return line.replace(ANSI_PATTERN, "");
12
+ };
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import { GuideOpenTab } from "@serverkgg/bridge/guides";
2
3
  import {
3
4
  BridgeAccess,
4
5
  BridgeAction,
@@ -8,6 +9,8 @@ import {
8
9
  BridgeKind,
9
10
  BridgeLayout,
10
11
  type BridgeManifest,
12
+ type BridgeSetupManifest,
13
+ BridgeSetupStepKind,
11
14
  BridgeUploadMode,
12
15
  } from "@serverkgg/bridge/protocol";
13
16
  import { bridgeManifestSchema } from "./panel";
@@ -98,6 +101,7 @@ const manifest = (): BridgeManifest => ({
98
101
  ],
99
102
  },
100
103
  terminal: null,
104
+ setup: null,
101
105
  modules: [
102
106
  {
103
107
  id: "broadcast",
@@ -159,6 +163,7 @@ const previewManifest = (): BridgeManifest => ({
159
163
  ],
160
164
  },
161
165
  terminal: null,
166
+ setup: null,
162
167
  modules: [
163
168
  {
164
169
  id: "transition",
@@ -337,6 +342,7 @@ const detailManifest = (): BridgeManifest => ({
337
342
  ],
338
343
  },
339
344
  terminal: null,
345
+ setup: null,
340
346
  modules: [
341
347
  {
342
348
  id: "modpackStatus",
@@ -412,6 +418,7 @@ const uploadManifest = (mode?: BridgeUploadMode): BridgeManifest => ({
412
418
  ],
413
419
  },
414
420
  terminal: null,
421
+ setup: null,
415
422
  modules: [
416
423
  {
417
424
  id: "pending",
@@ -449,3 +456,89 @@ describe("the upload mirror", () => {
449
456
  expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
450
457
  });
451
458
  });
459
+
460
+ const setupManifest = (setup: BridgeSetupManifest | null): BridgeManifest => ({
461
+ version: "1.0.0",
462
+ panel: null,
463
+ terminal: null,
464
+ setup,
465
+ modules: [],
466
+ events: [],
467
+ });
468
+
469
+ describe("the setup mirror", () => {
470
+ test("carries every step kind through unchanged", () => {
471
+ const source = setupManifest({
472
+ steps: [
473
+ {
474
+ kind: BridgeSetupStepKind.Driver,
475
+ id: "sign-in",
476
+ title: text("Sign in"),
477
+ help: text("Sign in with your account"),
478
+ requiresRunning: true,
479
+ },
480
+ {
481
+ kind: BridgeSetupStepKind.Form,
482
+ id: "name",
483
+ title: text("Name your server"),
484
+ required: false,
485
+ tab: "settings",
486
+ section: "server",
487
+ fields: [
488
+ "SERVER_NAME",
489
+ ],
490
+ },
491
+ {
492
+ kind: BridgeSetupStepKind.Open,
493
+ id: "invite",
494
+ title: text("Invite your friends"),
495
+ target: {
496
+ tab: GuideOpenTab.Access,
497
+ },
498
+ },
499
+ ],
500
+ });
501
+
502
+ expect(bridgeManifestSchema.parse(source).setup).toEqual(source.setup);
503
+ });
504
+
505
+ test("takes a manifest that declares no setup", () => {
506
+ expect(bridgeManifestSchema.parse(setupManifest(null)).setup).toBeNull();
507
+ });
508
+
509
+ test("defaults a manifest from an older bridge that carries no setup block to null", () => {
510
+ const older = setupManifest(null);
511
+
512
+ delete (older as Partial<BridgeManifest>).setup;
513
+
514
+ expect(bridgeManifestSchema.parse(older).setup).toBeNull();
515
+ });
516
+
517
+ test("refuses a step kind the platform does not know", () => {
518
+ const source = setupManifest({
519
+ steps: [
520
+ {
521
+ kind: "wizard" as BridgeSetupStepKind.Driver,
522
+ id: "sign-in",
523
+ title: text("Sign in"),
524
+ },
525
+ ],
526
+ });
527
+
528
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
529
+ });
530
+
531
+ test("refuses more steps than the platform takes", () => {
532
+ const source = setupManifest({
533
+ steps: Array.from({
534
+ length: 9,
535
+ }).map((_entry, index) => ({
536
+ kind: BridgeSetupStepKind.Driver as const,
537
+ id: `step-${index}`,
538
+ title: text(`Step ${index}`),
539
+ })),
540
+ });
541
+
542
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
543
+ });
544
+ });
package/src/wire/panel.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { guideOpenTargetSchema } from "@serverkgg/bridge/guides";
1
2
  import {
2
3
  BRIDGE_ID_PATTERN,
3
4
  BRIDGE_MODULE_ID_PATTERN,
@@ -11,6 +12,9 @@ import {
11
12
  type BridgeManifest,
12
13
  type BridgePanel,
13
14
  type BridgeSection,
15
+ type BridgeSetupManifest,
16
+ type BridgeSetupStep,
17
+ BridgeSetupStepKind,
14
18
  BridgeStream,
15
19
  type BridgeTerminalArg,
16
20
  type BridgeTerminalCommand,
@@ -30,6 +34,10 @@ const SECTION_ACTIONS_LIMIT = 10;
30
34
 
31
35
  const FORM_CLEARS_LIMIT = 8;
32
36
 
37
+ const SETUP_STEPS_LIMIT = 8;
38
+
39
+ const SETUP_STEP_FIELDS_LIMIT = 32;
40
+
33
41
  const zKey = z
34
42
  .string()
35
43
  .min(1)
@@ -276,6 +284,37 @@ export const bridgeTerminalManifestSchema: z.ZodType<BridgeTerminalManifest> = z
276
284
  rules: z.array(bridgeTerminalRuleSchema).max(64),
277
285
  });
278
286
 
287
+ const setupStepBase = {
288
+ id: zBridgeId,
289
+ title: panelTextSchema,
290
+ help: panelTextSchema.optional(),
291
+ required: z.boolean().optional(),
292
+ };
293
+
294
+ export const bridgeSetupStepSchema: z.ZodType<BridgeSetupStep> = z.discriminatedUnion("kind", [
295
+ z.object({
296
+ ...setupStepBase,
297
+ kind: z.literal(BridgeSetupStepKind.Driver),
298
+ requiresRunning: z.boolean().optional(),
299
+ }),
300
+ z.object({
301
+ ...setupStepBase,
302
+ kind: z.literal(BridgeSetupStepKind.Form),
303
+ tab: zBridgeId,
304
+ section: zBridgeId,
305
+ fields: z.array(zKey).max(SETUP_STEP_FIELDS_LIMIT).optional(),
306
+ }),
307
+ z.object({
308
+ ...setupStepBase,
309
+ kind: z.literal(BridgeSetupStepKind.Open),
310
+ target: guideOpenTargetSchema,
311
+ }),
312
+ ]);
313
+
314
+ export const bridgeSetupManifestSchema: z.ZodType<BridgeSetupManifest> = z.object({
315
+ steps: z.array(bridgeSetupStepSchema).min(1).max(SETUP_STEPS_LIMIT),
316
+ });
317
+
279
318
  export const bridgeModuleManifestSchema = z.object({
280
319
  id: zBridgeModuleId,
281
320
  kind: z.enum(BridgeKind),
@@ -290,6 +329,7 @@ export const bridgeManifestSchema: z.ZodType<BridgeManifest> = z.object({
290
329
  version: z.string(),
291
330
  panel: bridgePanelSchema.nullable(),
292
331
  terminal: bridgeTerminalManifestSchema.nullable().default(null),
332
+ setup: bridgeSetupManifestSchema.nullable().default(null),
293
333
  modules: z.array(bridgeModuleManifestSchema).max(32),
294
334
  events: z.array(z.string().min(1).max(64)).max(32),
295
335
  });