@serverkgg/bridge 0.0.7 → 0.0.9

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 (52) hide show
  1. package/package.json +4 -3
  2. package/src/cli/validate.ts +1 -0
  3. package/src/context/bridgeContext.ts +10 -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 +2 -0
  8. package/src/core/bridgeUserError.test.ts +23 -0
  9. package/src/core/bridgeUserError.ts +12 -0
  10. package/src/core/index.ts +3 -0
  11. package/src/manifest/index.ts +1 -0
  12. package/src/manifest/manifest.test.ts +85 -0
  13. package/src/manifest/manifest.ts +21 -0
  14. package/src/manifest/presence.ts +48 -0
  15. package/src/manifest/primitives.ts +6 -0
  16. package/src/modules/bridge.ts +45 -2
  17. package/src/modules/bridgeDriver.ts +2 -0
  18. package/src/modules/bridgeModule.ts +25 -3
  19. package/src/panel/bridgeDetail.ts +49 -0
  20. package/src/panel/bridgeLayout.ts +1 -0
  21. package/src/panel/bridgePanel.ts +30 -0
  22. package/src/panel/bridgeUploadMode.ts +4 -0
  23. package/src/panel/index.ts +2 -0
  24. package/src/protocol/bridgeEgress.ts +8 -1
  25. package/src/protocol/bridgeEvents.ts +4 -0
  26. package/src/protocol/bridgeFrame.ts +28 -1
  27. package/src/protocol/bridgeLimits.ts +10 -0
  28. package/src/runtime/bridgeActions.ts +7 -0
  29. package/src/runtime/bridgeDriver.test.ts +477 -2
  30. package/src/runtime/bridgeDriver.ts +116 -3
  31. package/src/runtime/bridgeEgress.ts +3 -0
  32. package/src/runtime/bridgeEvents.ts +7 -5
  33. package/src/runtime/bridgeFiles.test.ts +625 -15
  34. package/src/runtime/bridgeFiles.ts +152 -45
  35. package/src/runtime/bridgeInstall.test.ts +118 -0
  36. package/src/runtime/bridgeInstall.ts +55 -0
  37. package/src/runtime/bridgeNet.ts +32 -3
  38. package/src/runtime/bridgePending.test.ts +148 -0
  39. package/src/runtime/bridgePending.ts +43 -0
  40. package/src/runtime/bridgePrivilege.test.ts +77 -0
  41. package/src/runtime/bridgePrivilege.ts +5 -3
  42. package/src/runtime/bridgeRegistry.test.ts +134 -0
  43. package/src/runtime/bridgeRegistry.ts +26 -4
  44. package/src/runtime/bridgeServer.test.ts +338 -0
  45. package/src/runtime/bridgeServer.ts +25 -3
  46. package/src/runtime/bridgeSupervisor.ts +18 -0
  47. package/src/runtime/bridgeZip.ts +179 -58
  48. package/src/runtime/serve.ts +64 -4
  49. package/src/wire/index.ts +2 -0
  50. package/src/wire/panel.test.ts +451 -0
  51. package/src/wire/panel.ts +295 -0
  52. package/src/wire/zEnvKey.ts +7 -0
@@ -0,0 +1,451 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ BridgeAccess,
4
+ BridgeAction,
5
+ BridgeConfirm,
6
+ BridgeControl,
7
+ BridgeFormTarget,
8
+ BridgeKind,
9
+ BridgeLayout,
10
+ type BridgeManifest,
11
+ BridgeUploadMode,
12
+ } from "@serverkgg/bridge/protocol";
13
+ import { bridgeManifestSchema } from "./panel";
14
+
15
+ const text = (value: string) => ({
16
+ ar: value,
17
+ en: value,
18
+ });
19
+
20
+ const manifest = (): BridgeManifest => ({
21
+ version: "1.0.0",
22
+ panel: {
23
+ tabs: [
24
+ {
25
+ id: "gameplay",
26
+ title: text("Gameplay"),
27
+ access: BridgeAccess.Invoke,
28
+ sections: [
29
+ {
30
+ layout: BridgeLayout.Actions,
31
+ id: "broadcast",
32
+ title: text("Broadcast"),
33
+ help: text("Send a message to everyone playing"),
34
+ module: "broadcast",
35
+ actions: [
36
+ {
37
+ id: "say",
38
+ label: text("Say"),
39
+ fields: [
40
+ {
41
+ key: "message",
42
+ control: BridgeControl.Text,
43
+ label: text("Message"),
44
+ maxLength: 200,
45
+ },
46
+ ],
47
+ },
48
+ {
49
+ id: "saveAll",
50
+ label: text("Save"),
51
+ confirm: BridgeConfirm.Normal,
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ layout: BridgeLayout.Table,
57
+ id: "players",
58
+ module: "players",
59
+ columns: [
60
+ {
61
+ key: "name",
62
+ label: text("Name"),
63
+ },
64
+ ],
65
+ actions: [
66
+ {
67
+ id: "xp",
68
+ label: text("Give XP"),
69
+ fields: [
70
+ {
71
+ key: "amount",
72
+ control: BridgeControl.Number,
73
+ label: text("Amount"),
74
+ min: 1,
75
+ max: 1000,
76
+ },
77
+ {
78
+ key: "kind",
79
+ control: BridgeControl.Select,
80
+ label: text("Kind"),
81
+ options: [
82
+ {
83
+ value: "levels",
84
+ label: text("Levels"),
85
+ },
86
+ {
87
+ value: "points",
88
+ label: text("Points"),
89
+ },
90
+ ],
91
+ },
92
+ ],
93
+ },
94
+ ],
95
+ },
96
+ ],
97
+ },
98
+ ],
99
+ },
100
+ terminal: null,
101
+ modules: [
102
+ {
103
+ id: "broadcast",
104
+ kind: BridgeKind.Actions,
105
+ requiresRunning: true,
106
+ refreshSeconds: null,
107
+ pageSize: null,
108
+ dependsOn: [],
109
+ actions: [
110
+ "say",
111
+ "saveAll",
112
+ ],
113
+ },
114
+ {
115
+ id: "players",
116
+ kind: BridgeKind.Collection,
117
+ requiresRunning: true,
118
+ refreshSeconds: 10,
119
+ pageSize: null,
120
+ dependsOn: [],
121
+ actions: [
122
+ "list",
123
+ "xp",
124
+ ],
125
+ },
126
+ ],
127
+ events: [
128
+ "PlayerJoined",
129
+ ],
130
+ });
131
+
132
+ const previewManifest = (): BridgeManifest => ({
133
+ version: "1.0.0",
134
+ panel: {
135
+ tabs: [
136
+ {
137
+ id: "version",
138
+ title: text("Version"),
139
+ sections: [
140
+ {
141
+ layout: BridgeLayout.Form,
142
+ id: "version",
143
+ target: BridgeFormTarget.Variables,
144
+ preview: {
145
+ module: "transition",
146
+ },
147
+ reinstall: true,
148
+ confirm: BridgeConfirm.Strong,
149
+ fields: [
150
+ {
151
+ key: "MC_VERSION",
152
+ control: BridgeControl.Text,
153
+ label: text("Version"),
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ },
159
+ ],
160
+ },
161
+ terminal: null,
162
+ modules: [
163
+ {
164
+ id: "transition",
165
+ kind: BridgeKind.Preview,
166
+ requiresRunning: false,
167
+ refreshSeconds: null,
168
+ pageSize: null,
169
+ dependsOn: [],
170
+ actions: [
171
+ BridgeAction.Preview,
172
+ ],
173
+ },
174
+ ],
175
+ events: [],
176
+ });
177
+
178
+ const tabsOf = (count: number) => {
179
+ return Array.from({
180
+ length: count,
181
+ }).map((_entry, index) => ({
182
+ id: `tab-${index}`,
183
+ title: text(`Tab ${index}`),
184
+ sections: [
185
+ {
186
+ layout: BridgeLayout.Actions,
187
+ id: "broadcast",
188
+ module: "broadcast",
189
+ actions: [
190
+ {
191
+ id: "say",
192
+ label: text("Say"),
193
+ },
194
+ ],
195
+ },
196
+ ],
197
+ }));
198
+ };
199
+
200
+ describe("the panel mirror", () => {
201
+ test("carries an actions section and its fields through unchanged", () => {
202
+ const source = manifest();
203
+ const parsed = bridgeManifestSchema.parse(source);
204
+
205
+ expect(parsed).toEqual(source);
206
+ });
207
+
208
+ test("refuses an action carrying more fields than a prompt fits", () => {
209
+ const source = manifest();
210
+ const [tab] = source.panel?.tabs ?? [];
211
+ const [section] = tab?.sections ?? [];
212
+
213
+ if (section?.layout !== BridgeLayout.Actions) {
214
+ throw new Error("fixture is missing its actions section");
215
+ }
216
+
217
+ const [action] = section.actions;
218
+
219
+ if (!action) {
220
+ throw new Error("fixture is missing its action");
221
+ }
222
+
223
+ action.fields = Array.from({
224
+ length: 6,
225
+ }).map((_entry, index) => ({
226
+ key: `field${index}`,
227
+ control: BridgeControl.Text,
228
+ label: text("Field"),
229
+ }));
230
+
231
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
232
+ });
233
+
234
+ test("keeps a variables form's preview ref instead of stripping it", () => {
235
+ const source = previewManifest();
236
+ const parsed = bridgeManifestSchema.parse(source);
237
+ const [section] = parsed.panel?.tabs[0]?.sections ?? [];
238
+
239
+ if (section?.layout !== BridgeLayout.Form) {
240
+ throw new Error("fixture is missing its form section");
241
+ }
242
+
243
+ expect(section.preview).toEqual({
244
+ module: "transition",
245
+ });
246
+ expect(parsed).toEqual(source);
247
+ });
248
+
249
+ test("takes a preview module and the preview action it answers", () => {
250
+ const parsed = bridgeManifestSchema.parse(previewManifest());
251
+
252
+ expect(parsed.modules[0]).toEqual({
253
+ id: "transition",
254
+ kind: BridgeKind.Preview,
255
+ requiresRunning: false,
256
+ refreshSeconds: null,
257
+ pageSize: null,
258
+ dependsOn: [],
259
+ actions: [
260
+ BridgeAction.Preview,
261
+ ],
262
+ });
263
+ });
264
+
265
+ test("takes eight tabs and refuses nine", () => {
266
+ const source = manifest();
267
+
268
+ expect(
269
+ bridgeManifestSchema.safeParse({
270
+ ...source,
271
+ panel: {
272
+ tabs: tabsOf(8),
273
+ },
274
+ }).success,
275
+ ).toBe(true);
276
+
277
+ expect(
278
+ bridgeManifestSchema.safeParse({
279
+ ...source,
280
+ panel: {
281
+ tabs: tabsOf(9),
282
+ },
283
+ }).success,
284
+ ).toBe(false);
285
+ });
286
+ });
287
+
288
+ const detailManifest = (): BridgeManifest => ({
289
+ version: "1.0.0",
290
+ panel: {
291
+ tabs: [
292
+ {
293
+ id: "version",
294
+ title: text("Version"),
295
+ sections: [
296
+ {
297
+ layout: BridgeLayout.Form,
298
+ id: "version",
299
+ target: BridgeFormTarget.Variables,
300
+ reinstall: true,
301
+ fields: [
302
+ {
303
+ key: "MC_VERSION",
304
+ control: BridgeControl.Text,
305
+ label: text("Version"),
306
+ },
307
+ ],
308
+ clears: [
309
+ {
310
+ variable: "MODPACK",
311
+ warning: text("Applying this removes the modpack"),
312
+ },
313
+ ],
314
+ },
315
+ {
316
+ layout: BridgeLayout.Detail,
317
+ id: "modpack-status",
318
+ title: text("Running a modpack"),
319
+ module: "modpackStatus",
320
+ reinstall: true,
321
+ variable: "MODPACK",
322
+ confirm: BridgeConfirm.Strong,
323
+ related: {
324
+ tab: "modpacks",
325
+ label: text("Browse modpacks"),
326
+ },
327
+ actions: [
328
+ {
329
+ id: "update",
330
+ label: text("Update"),
331
+ confirm: BridgeConfirm.Strong,
332
+ },
333
+ ],
334
+ },
335
+ ],
336
+ },
337
+ ],
338
+ },
339
+ terminal: null,
340
+ modules: [
341
+ {
342
+ id: "modpackStatus",
343
+ kind: BridgeKind.Detail,
344
+ requiresRunning: false,
345
+ refreshSeconds: null,
346
+ pageSize: null,
347
+ dependsOn: [],
348
+ actions: [
349
+ BridgeAction.Read,
350
+ "update",
351
+ ],
352
+ },
353
+ ],
354
+ events: [],
355
+ });
356
+
357
+ describe("detail sections", () => {
358
+ test("round-trips a detail section and a clearing form", () => {
359
+ const source = detailManifest();
360
+
361
+ expect(bridgeManifestSchema.parse(source)).toEqual(source);
362
+ });
363
+
364
+ test("refuses a clear whose variable is not an env key", () => {
365
+ const source = detailManifest();
366
+ const [form] = source.panel?.tabs[0]?.sections ?? [];
367
+
368
+ if (form?.layout !== BridgeLayout.Form) {
369
+ throw new Error("fixture is missing its form section");
370
+ }
371
+
372
+ form.clears = [
373
+ {
374
+ variable: "mod pack",
375
+ warning: text("Broken"),
376
+ },
377
+ ];
378
+
379
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
380
+ });
381
+ });
382
+
383
+ const uploadManifest = (mode?: BridgeUploadMode): BridgeManifest => ({
384
+ version: "1.0.0",
385
+ panel: {
386
+ tabs: [
387
+ {
388
+ id: "modpacks",
389
+ title: text("Modpacks"),
390
+ sections: [
391
+ {
392
+ layout: BridgeLayout.Table,
393
+ id: "pending",
394
+ module: "pending",
395
+ columns: [
396
+ {
397
+ key: "name",
398
+ label: text("Name"),
399
+ },
400
+ ],
401
+ upload: {
402
+ label: text("Upload"),
403
+ extensions: [
404
+ "jar",
405
+ ],
406
+ staging: ".serverk-staging/pending-upload",
407
+ mode,
408
+ },
409
+ },
410
+ ],
411
+ },
412
+ ],
413
+ },
414
+ terminal: null,
415
+ modules: [
416
+ {
417
+ id: "pending",
418
+ kind: BridgeKind.Collection,
419
+ requiresRunning: false,
420
+ refreshSeconds: null,
421
+ pageSize: null,
422
+ dependsOn: [],
423
+ actions: [],
424
+ },
425
+ ],
426
+ events: [],
427
+ });
428
+
429
+ describe("the upload mirror", () => {
430
+ test("carries the file upload mode through unchanged", () => {
431
+ const source = uploadManifest(BridgeUploadMode.File);
432
+ const parsed = bridgeManifestSchema.parse(source);
433
+ const [section] = parsed.panel?.tabs[0]?.sections ?? [];
434
+
435
+ if (section?.layout !== BridgeLayout.Table) {
436
+ throw new Error("fixture is missing its table section");
437
+ }
438
+
439
+ expect(section.upload?.mode).toBe(BridgeUploadMode.File);
440
+ });
441
+
442
+ test("takes an upload that declares no mode", () => {
443
+ expect(bridgeManifestSchema.safeParse(uploadManifest()).success).toBe(true);
444
+ });
445
+
446
+ test("refuses an upload mode the platform does not know", () => {
447
+ const source = uploadManifest("stream" as BridgeUploadMode);
448
+
449
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
450
+ });
451
+ });