@serverkgg/bridge 0.0.8 → 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 (72) hide show
  1. package/package.json +3 -3
  2. package/src/cli/validate.ts +2 -0
  3. package/src/context/bridgeContext.ts +17 -0
  4. package/src/context/index.ts +1 -0
  5. package/src/core/bridgeDownloadError.ts +10 -0
  6. package/src/core/bridgeHold.ts +12 -0
  7. package/src/core/bridgeKind.ts +3 -0
  8. package/src/core/bridgeProbeError.ts +16 -0
  9. package/src/core/bridgeSetup.ts +20 -0
  10. package/src/core/bridgeUserError.test.ts +23 -0
  11. package/src/core/bridgeUserError.ts +12 -0
  12. package/src/core/bridgeValue.test.ts +29 -0
  13. package/src/core/bridgeValue.ts +12 -0
  14. package/src/core/index.ts +5 -0
  15. package/src/guides/blocks.ts +8 -0
  16. package/src/guides/markdown.test.ts +18 -0
  17. package/src/guides/markdown.ts +7 -1
  18. package/src/manifest/index.ts +1 -0
  19. package/src/manifest/manifest.test.ts +132 -0
  20. package/src/manifest/manifest.ts +21 -0
  21. package/src/manifest/presence.ts +56 -0
  22. package/src/manifest/primitives.ts +6 -0
  23. package/src/modules/bridge.ts +65 -2
  24. package/src/modules/bridgeDriver.ts +4 -0
  25. package/src/modules/bridgeModule.ts +32 -3
  26. package/src/panel/bridgeDetail.ts +49 -0
  27. package/src/panel/bridgeLayout.ts +1 -0
  28. package/src/panel/bridgePanel.ts +30 -0
  29. package/src/panel/bridgeUploadMode.ts +4 -0
  30. package/src/panel/index.ts +2 -0
  31. package/src/probes/a2s.test.ts +71 -0
  32. package/src/probes/a2s.ts +38 -11
  33. package/src/protocol/bridgeEgress.ts +8 -1
  34. package/src/protocol/bridgeEvents.ts +4 -0
  35. package/src/protocol/bridgeFrame.ts +33 -1
  36. package/src/protocol/bridgeLimits.ts +12 -0
  37. package/src/protocol/bridgeManifest.ts +2 -0
  38. package/src/protocol/bridgeSetup.ts +97 -0
  39. package/src/protocol/index.ts +1 -0
  40. package/src/runtime/bridgeActions.ts +7 -0
  41. package/src/runtime/bridgeConfigReader.ts +12 -8
  42. package/src/runtime/bridgeContext.ts +28 -2
  43. package/src/runtime/bridgeDriver.test.ts +748 -3
  44. package/src/runtime/bridgeDriver.ts +198 -4
  45. package/src/runtime/bridgeEgress.ts +3 -0
  46. package/src/runtime/bridgeEvents.test.ts +96 -0
  47. package/src/runtime/bridgeEvents.ts +11 -6
  48. package/src/runtime/bridgeFiles.test.ts +625 -15
  49. package/src/runtime/bridgeFiles.ts +152 -45
  50. package/src/runtime/bridgeInstall.test.ts +118 -0
  51. package/src/runtime/bridgeInstall.ts +55 -0
  52. package/src/runtime/bridgeNet.ts +32 -3
  53. package/src/runtime/bridgePending.test.ts +148 -0
  54. package/src/runtime/bridgePending.ts +43 -0
  55. package/src/runtime/bridgeRegistry.test.ts +172 -1
  56. package/src/runtime/bridgeRegistry.ts +40 -5
  57. package/src/runtime/bridgeSecrets.test.ts +121 -0
  58. package/src/runtime/bridgeSecrets.ts +43 -0
  59. package/src/runtime/bridgeServer.test.ts +598 -0
  60. package/src/runtime/bridgeServer.ts +42 -3
  61. package/src/runtime/bridgeSetup.test.ts +189 -0
  62. package/src/runtime/bridgeSetup.ts +63 -0
  63. package/src/runtime/bridgeSignal.test.ts +114 -0
  64. package/src/runtime/bridgeSignal.ts +31 -0
  65. package/src/runtime/bridgeSupervisor.test.ts +111 -0
  66. package/src/runtime/bridgeSupervisor.ts +55 -5
  67. package/src/runtime/bridgeZip.ts +179 -58
  68. package/src/runtime/serve.ts +115 -4
  69. package/src/utils/index.ts +1 -0
  70. package/src/utils/stripAnsi.ts +12 -0
  71. package/src/wire/panel.test.ts +259 -0
  72. package/src/wire/panel.ts +72 -0
@@ -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,9 @@ import {
8
9
  BridgeKind,
9
10
  BridgeLayout,
10
11
  type BridgeManifest,
12
+ type BridgeSetupManifest,
13
+ BridgeSetupStepKind,
14
+ BridgeUploadMode,
11
15
  } from "@serverkgg/bridge/protocol";
12
16
  import { bridgeManifestSchema } from "./panel";
13
17
 
@@ -97,6 +101,7 @@ const manifest = (): BridgeManifest => ({
97
101
  ],
98
102
  },
99
103
  terminal: null,
104
+ setup: null,
100
105
  modules: [
101
106
  {
102
107
  id: "broadcast",
@@ -158,6 +163,7 @@ const previewManifest = (): BridgeManifest => ({
158
163
  ],
159
164
  },
160
165
  terminal: null,
166
+ setup: null,
161
167
  modules: [
162
168
  {
163
169
  id: "transition",
@@ -283,3 +289,256 @@ describe("the panel mirror", () => {
283
289
  ).toBe(false);
284
290
  });
285
291
  });
292
+
293
+ const detailManifest = (): BridgeManifest => ({
294
+ version: "1.0.0",
295
+ panel: {
296
+ tabs: [
297
+ {
298
+ id: "version",
299
+ title: text("Version"),
300
+ sections: [
301
+ {
302
+ layout: BridgeLayout.Form,
303
+ id: "version",
304
+ target: BridgeFormTarget.Variables,
305
+ reinstall: true,
306
+ fields: [
307
+ {
308
+ key: "MC_VERSION",
309
+ control: BridgeControl.Text,
310
+ label: text("Version"),
311
+ },
312
+ ],
313
+ clears: [
314
+ {
315
+ variable: "MODPACK",
316
+ warning: text("Applying this removes the modpack"),
317
+ },
318
+ ],
319
+ },
320
+ {
321
+ layout: BridgeLayout.Detail,
322
+ id: "modpack-status",
323
+ title: text("Running a modpack"),
324
+ module: "modpackStatus",
325
+ reinstall: true,
326
+ variable: "MODPACK",
327
+ confirm: BridgeConfirm.Strong,
328
+ related: {
329
+ tab: "modpacks",
330
+ label: text("Browse modpacks"),
331
+ },
332
+ actions: [
333
+ {
334
+ id: "update",
335
+ label: text("Update"),
336
+ confirm: BridgeConfirm.Strong,
337
+ },
338
+ ],
339
+ },
340
+ ],
341
+ },
342
+ ],
343
+ },
344
+ terminal: null,
345
+ setup: null,
346
+ modules: [
347
+ {
348
+ id: "modpackStatus",
349
+ kind: BridgeKind.Detail,
350
+ requiresRunning: false,
351
+ refreshSeconds: null,
352
+ pageSize: null,
353
+ dependsOn: [],
354
+ actions: [
355
+ BridgeAction.Read,
356
+ "update",
357
+ ],
358
+ },
359
+ ],
360
+ events: [],
361
+ });
362
+
363
+ describe("detail sections", () => {
364
+ test("round-trips a detail section and a clearing form", () => {
365
+ const source = detailManifest();
366
+
367
+ expect(bridgeManifestSchema.parse(source)).toEqual(source);
368
+ });
369
+
370
+ test("refuses a clear whose variable is not an env key", () => {
371
+ const source = detailManifest();
372
+ const [form] = source.panel?.tabs[0]?.sections ?? [];
373
+
374
+ if (form?.layout !== BridgeLayout.Form) {
375
+ throw new Error("fixture is missing its form section");
376
+ }
377
+
378
+ form.clears = [
379
+ {
380
+ variable: "mod pack",
381
+ warning: text("Broken"),
382
+ },
383
+ ];
384
+
385
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
386
+ });
387
+ });
388
+
389
+ const uploadManifest = (mode?: BridgeUploadMode): BridgeManifest => ({
390
+ version: "1.0.0",
391
+ panel: {
392
+ tabs: [
393
+ {
394
+ id: "modpacks",
395
+ title: text("Modpacks"),
396
+ sections: [
397
+ {
398
+ layout: BridgeLayout.Table,
399
+ id: "pending",
400
+ module: "pending",
401
+ columns: [
402
+ {
403
+ key: "name",
404
+ label: text("Name"),
405
+ },
406
+ ],
407
+ upload: {
408
+ label: text("Upload"),
409
+ extensions: [
410
+ "jar",
411
+ ],
412
+ staging: ".serverk-staging/pending-upload",
413
+ mode,
414
+ },
415
+ },
416
+ ],
417
+ },
418
+ ],
419
+ },
420
+ terminal: null,
421
+ setup: null,
422
+ modules: [
423
+ {
424
+ id: "pending",
425
+ kind: BridgeKind.Collection,
426
+ requiresRunning: false,
427
+ refreshSeconds: null,
428
+ pageSize: null,
429
+ dependsOn: [],
430
+ actions: [],
431
+ },
432
+ ],
433
+ events: [],
434
+ });
435
+
436
+ describe("the upload mirror", () => {
437
+ test("carries the file upload mode through unchanged", () => {
438
+ const source = uploadManifest(BridgeUploadMode.File);
439
+ const parsed = bridgeManifestSchema.parse(source);
440
+ const [section] = parsed.panel?.tabs[0]?.sections ?? [];
441
+
442
+ if (section?.layout !== BridgeLayout.Table) {
443
+ throw new Error("fixture is missing its table section");
444
+ }
445
+
446
+ expect(section.upload?.mode).toBe(BridgeUploadMode.File);
447
+ });
448
+
449
+ test("takes an upload that declares no mode", () => {
450
+ expect(bridgeManifestSchema.safeParse(uploadManifest()).success).toBe(true);
451
+ });
452
+
453
+ test("refuses an upload mode the platform does not know", () => {
454
+ const source = uploadManifest("stream" as BridgeUploadMode);
455
+
456
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
457
+ });
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,12 +12,16 @@ 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,
17
21
  BridgeTerminalLevel,
18
22
  type BridgeTerminalManifest,
19
23
  type BridgeTerminalRuleManifest,
24
+ BridgeUploadMode,
20
25
  } from "@serverkgg/bridge/protocol";
21
26
  import { z } from "zod";
22
27
  import { zEnvKey } from "./zEnvKey";
@@ -27,6 +32,12 @@ const ACTION_FIELDS_LIMIT = 5;
27
32
 
28
33
  const SECTION_ACTIONS_LIMIT = 10;
29
34
 
35
+ const FORM_CLEARS_LIMIT = 8;
36
+
37
+ const SETUP_STEPS_LIMIT = 8;
38
+
39
+ const SETUP_STEP_FIELDS_LIMIT = 32;
40
+
30
41
  const zKey = z
31
42
  .string()
32
43
  .min(1)
@@ -107,6 +118,16 @@ export const bridgeSectionActionSchema = z.object({
107
118
  fields: z.array(bridgeFieldSchema).max(ACTION_FIELDS_LIMIT).optional(),
108
119
  });
109
120
 
121
+ export const bridgeTabRefSchema = z.object({
122
+ tab: zBridgeId,
123
+ label: panelTextSchema,
124
+ });
125
+
126
+ export const bridgeFormClearSchema = z.object({
127
+ variable: zEnvKey,
128
+ warning: panelTextSchema,
129
+ });
130
+
110
131
  export const bridgeSectionAddSchema = z.object({
111
132
  label: panelTextSchema,
112
133
  placeholder: z.string().max(256).optional(),
@@ -116,6 +137,7 @@ export const bridgeSectionUploadSchema = z.object({
116
137
  label: panelTextSchema,
117
138
  extensions: z.array(z.string().min(1).max(16)).min(1).max(8),
118
139
  staging: z.string().min(1).max(128),
140
+ mode: z.enum(BridgeUploadMode).optional(),
119
141
  });
120
142
 
121
143
  export const bridgeActionsSectionSchema = z.object({
@@ -141,6 +163,23 @@ export const bridgeFormSectionSchema = z.object({
141
163
  confirm: z.enum(BridgeConfirm).optional(),
142
164
  confirmText: panelTextSchema.optional(),
143
165
  fields: z.array(bridgeFieldSchema).min(1).max(32),
166
+ clears: z.array(bridgeFormClearSchema).max(FORM_CLEARS_LIMIT).optional(),
167
+ visibleWhen: bridgeVisibleWhenSchema.optional(),
168
+ access: z.enum(BridgeAccess).optional(),
169
+ });
170
+
171
+ export const bridgeDetailSectionSchema = z.object({
172
+ layout: z.literal(BridgeLayout.Detail),
173
+ id: zBridgeId,
174
+ title: panelTextSchema.optional(),
175
+ module: zBridgeModuleId,
176
+ actions: z.array(bridgeSectionActionSchema).max(SECTION_ACTIONS_LIMIT).optional(),
177
+ reinstall: z.boolean().optional(),
178
+ variable: zEnvKey.optional(),
179
+ confirm: z.enum(BridgeConfirm).optional(),
180
+ confirmText: panelTextSchema.optional(),
181
+ related: bridgeTabRefSchema.optional(),
182
+ empty: panelTextSchema.optional(),
144
183
  visibleWhen: bridgeVisibleWhenSchema.optional(),
145
184
  access: z.enum(BridgeAccess).optional(),
146
185
  });
@@ -197,6 +236,7 @@ export const bridgeSectionSchema: z.ZodType<BridgeSection> = z.discriminatedUnio
197
236
  bridgeActionsSectionSchema,
198
237
  bridgeCardsSectionSchema,
199
238
  bridgeCatalogSectionSchema,
239
+ bridgeDetailSectionSchema,
200
240
  bridgeFormSectionSchema,
201
241
  bridgeTableSectionSchema,
202
242
  ]);
@@ -244,6 +284,37 @@ export const bridgeTerminalManifestSchema: z.ZodType<BridgeTerminalManifest> = z
244
284
  rules: z.array(bridgeTerminalRuleSchema).max(64),
245
285
  });
246
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
+
247
318
  export const bridgeModuleManifestSchema = z.object({
248
319
  id: zBridgeModuleId,
249
320
  kind: z.enum(BridgeKind),
@@ -258,6 +329,7 @@ export const bridgeManifestSchema: z.ZodType<BridgeManifest> = z.object({
258
329
  version: z.string(),
259
330
  panel: bridgePanelSchema.nullable(),
260
331
  terminal: bridgeTerminalManifestSchema.nullable().default(null),
332
+ setup: bridgeSetupManifestSchema.nullable().default(null),
261
333
  modules: z.array(bridgeModuleManifestSchema).max(32),
262
334
  events: z.array(z.string().min(1).max(64)).max(32),
263
335
  });