@serverkgg/bridge 0.0.18 → 0.0.19

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverkgg/bridge",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "description": "Serverk game bridge and driver runtime, wire protocol, manifest schema, and maintainer CLI",
5
5
  "homepage": "https://serverk.gg",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import type { BridgeRow } from "./bridgeValue";
2
3
  import { isLocalizedText } from "./bridgeValue";
3
4
 
4
5
  describe("isLocalizedText", () => {
@@ -27,3 +28,20 @@ describe("isLocalizedText", () => {
27
28
  ).toBe(false);
28
29
  });
29
30
  });
31
+
32
+ describe("BridgeRow", () => {
33
+ test('carries a localized cell beside its scalars so a driver never writes "English / عربي" into one string', () => {
34
+ const row: BridgeRow = {
35
+ id: "u-1",
36
+ kills: 12,
37
+ banned: false,
38
+ status: {
39
+ ar: "متصل",
40
+ en: "Online",
41
+ },
42
+ };
43
+
44
+ expect(isLocalizedText(row.status)).toBe(true);
45
+ expect(isLocalizedText(row.kills)).toBe(false);
46
+ });
47
+ });
@@ -2,15 +2,19 @@ export type BridgeValue = boolean | number | string | null;
2
2
 
3
3
  export type BridgeValues = Record<string, BridgeValue>;
4
4
 
5
- export type BridgeRow = BridgeValues & {
6
- id: string;
7
- };
8
-
9
5
  export interface LocalizedText {
10
6
  ar: string;
11
7
  en: string;
12
8
  }
13
9
 
10
+ export type BridgeCell = BridgeValue | LocalizedText;
11
+
12
+ export type BridgeCells = Record<string, BridgeCell>;
13
+
14
+ export type BridgeRow = BridgeCells & {
15
+ id: string;
16
+ };
17
+
14
18
  export type BridgeCopy = LocalizedText | string;
15
19
 
16
20
  export const isLocalizedText = (value: unknown): value is LocalizedText => {
@@ -1,5 +1,7 @@
1
1
  import type { BridgeContext, BridgeExecResult, BridgeFileEntry, BridgeReply } from "../context";
2
2
  import type {
3
+ BridgeCell,
4
+ BridgeCells,
3
5
  BridgeCopy,
4
6
  BridgeInstallHold,
5
7
  BridgeInstallOutcome,
@@ -83,6 +85,8 @@ export namespace Bridge {
83
85
  export type CatalogPage = BridgeCatalogPage;
84
86
  export type CatalogProvider = BridgeCatalogProvider;
85
87
  export type CatalogQuery = BridgeCatalogQuery;
88
+ export type Cell = BridgeCell;
89
+ export type Cells = BridgeCells;
86
90
  export type Collection<Row extends BridgeRow = BridgeRow> = BridgeCollectionModule<Row>;
87
91
  export type Context = BridgeContext;
88
92
  export type Copy = BridgeCopy;
@@ -286,6 +286,35 @@ describe("rconAccessSections", () => {
286
286
  expect(detail.empty).toBeDefined();
287
287
  });
288
288
 
289
+ test("the two sections carry different titles so the panel never repeats a heading", () => {
290
+ const [form, detail] = rconAccessSections();
291
+
292
+ expect(form?.title).toEqual({
293
+ ar: "الوصول عن بُعد",
294
+ en: "Remote access",
295
+ });
296
+ expect(detail?.title).toEqual({
297
+ ar: "بيانات الوصول عن بُعد",
298
+ en: "Remote access details",
299
+ });
300
+ expect(detail?.title).not.toEqual(form?.title);
301
+ });
302
+
303
+ test("the detail is visible only while remote access is on", () => {
304
+ const [, detail] = rconAccessSections();
305
+
306
+ if (detail?.layout !== BridgeLayout.Detail) {
307
+ throw new Error("the second section is the detail");
308
+ }
309
+
310
+ expect(detail.visibleWhen).toEqual({
311
+ variable: RCON_ACCESS_VARIABLE,
312
+ values: [
313
+ "true",
314
+ ],
315
+ });
316
+ });
317
+
289
318
  test("a dedicated port opens a port and a shared one only turns access on", () => {
290
319
  const [dedicated] = rconAccessSections({
291
320
  portKey: RCON_ACCESS_PORT,
@@ -49,6 +49,11 @@ const ACCESS_TITLE: LocalizedText = {
49
49
  en: "Remote access",
50
50
  };
51
51
 
52
+ const ACCESS_DETAIL_TITLE: LocalizedText = {
53
+ ar: "بيانات الوصول عن بُعد",
54
+ en: "Remote access details",
55
+ };
56
+
52
57
  const ACCESS_DESCRIPTION: LocalizedText = {
53
58
  ar: "الوصول عن بُعد يخلي أداة خارجية تتحكم بسيرفرك بكلمة المرور هذي. الكلمة تمشي على النت بدون تشفير، فاقفل الوصول لما ما فيه أداة تستخدمه، ولا تعطي الكلمة أحد. لو تسربت غيّرها على طول.",
54
59
  en: "Remote access lets an external tool control your server with this password. The password crosses the internet in the clear, so turn remote access off when no tool is using it, and never share the password. Rotate it the moment it leaks.",
@@ -235,7 +240,7 @@ export const rconAccessSections = (options: RconAccessSectionsOptions = {}): Bri
235
240
  {
236
241
  layout: BridgeLayout.Detail,
237
242
  id: DETAIL_SECTION_ID,
238
- title: ACCESS_TITLE,
243
+ title: ACCESS_DETAIL_TITLE,
239
244
  module: RCON_ACCESS_MODULE,
240
245
  actions: [
241
246
  {
@@ -255,6 +260,12 @@ export const rconAccessSections = (options: RconAccessSectionsOptions = {}): Bri
255
260
  ar: "شغّل سيرفرك مرة عشان تتولد كلمة المرور.",
256
261
  en: "Start your server once to generate the password.",
257
262
  },
263
+ visibleWhen: {
264
+ variable: RCON_ACCESS_VARIABLE,
265
+ values: [
266
+ "true",
267
+ ],
268
+ },
258
269
  },
259
270
  ];
260
271
  };
@@ -15,7 +15,7 @@ import {
15
15
  BridgeSetupStepKind,
16
16
  BridgeUploadMode,
17
17
  } from "@serverkgg/bridge/protocol";
18
- import { bridgeManifestSchema } from "./panel";
18
+ import { bridgeCellsSchema, bridgeManifestSchema } from "./panel";
19
19
 
20
20
  const text = (value: string) => ({
21
21
  ar: value,
@@ -683,3 +683,37 @@ describe("the placement mirror", () => {
683
683
  expect(bridgeManifestSchema.parse(source)).toEqual(source);
684
684
  });
685
685
  });
686
+
687
+ describe("the cell schema", () => {
688
+ test("accepts a scalar and a localized cell", () => {
689
+ expect(
690
+ bridgeCellsSchema.parse({
691
+ id: "u-1",
692
+ kills: 12,
693
+ banned: false,
694
+ status: {
695
+ ar: "متصل",
696
+ en: "Online",
697
+ },
698
+ }),
699
+ ).toEqual({
700
+ id: "u-1",
701
+ kills: 12,
702
+ banned: false,
703
+ status: {
704
+ ar: "متصل",
705
+ en: "Online",
706
+ },
707
+ });
708
+ });
709
+
710
+ test("rejects a cell that carries only one language", () => {
711
+ expect(
712
+ bridgeCellsSchema.safeParse({
713
+ status: {
714
+ ar: "متصل",
715
+ },
716
+ }).success,
717
+ ).toBe(false);
718
+ });
719
+ });
package/src/wire/panel.ts CHANGED
@@ -68,6 +68,17 @@ export const panelTextSchema = z.object({
68
68
  en: z.string().min(1).max(PANEL_TEXT_LENGTH_LIMIT),
69
69
  });
70
70
 
71
+ export const bridgeCellSchema = z.union([
72
+ bridgeValueSchema,
73
+ panelTextSchema,
74
+ ]);
75
+
76
+ export type BridgeCellInput = z.infer<typeof bridgeCellSchema>;
77
+
78
+ export const bridgeCellsSchema = z.record(zKey, bridgeCellSchema);
79
+
80
+ export type BridgeCellsInput = z.infer<typeof bridgeCellsSchema>;
81
+
71
82
  export const bridgeOptionSchema = z.object({
72
83
  value: z.string().min(1).max(128),
73
84
  label: panelTextSchema,