@serverkgg/bridge 0.1.14 → 0.1.15

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.
@@ -3,6 +3,8 @@ import { GuideOpenTab } from "@serverkgg/bridge/guides";
3
3
  import {
4
4
  BridgeAccess,
5
5
  BridgeAction,
6
+ type BridgeCatalogSource,
7
+ BridgeCatalogSourceKind,
6
8
  BridgeConfirm,
7
9
  BridgeControl,
8
10
  type BridgeField,
@@ -11,6 +13,7 @@ import {
11
13
  BridgeLayout,
12
14
  type BridgeManifest,
13
15
  BridgePlace,
16
+ type BridgeSectionUpload,
14
17
  type BridgeSetupManifest,
15
18
  BridgeSetupStepKind,
16
19
  type BridgeTerminalManifest,
@@ -460,6 +463,143 @@ describe("the upload mirror", () => {
460
463
  });
461
464
  });
462
465
 
466
+ const MODS_UPLOAD: BridgeSectionUpload = {
467
+ label: text("Upload a mod"),
468
+ extensions: [
469
+ "zip",
470
+ ],
471
+ staging: ".serverk-staging/mods-upload",
472
+ mode: BridgeUploadMode.Archive,
473
+ };
474
+
475
+ const catalogManifest = (source?: BridgeCatalogSource, upload?: BridgeSectionUpload): BridgeManifest => ({
476
+ version: "1.0.0",
477
+ panel: {
478
+ tabs: [
479
+ {
480
+ id: "mods",
481
+ title: text("Mods"),
482
+ sections: [
483
+ {
484
+ layout: BridgeLayout.Catalog,
485
+ id: "mods",
486
+ module: "mods",
487
+ ...(upload
488
+ ? {
489
+ upload,
490
+ }
491
+ : {}),
492
+ },
493
+ ],
494
+ },
495
+ ],
496
+ },
497
+ terminal: null,
498
+ setup: null,
499
+ modules: [
500
+ {
501
+ id: "mods",
502
+ kind: BridgeKind.Catalog,
503
+ requiresRunning: false,
504
+ refreshSeconds: null,
505
+ pageSize: null,
506
+ dependsOn: [],
507
+ actions: [
508
+ BridgeAction.Preview,
509
+ BridgeAction.Install,
510
+ BridgeAction.Installed,
511
+ BridgeAction.Remove,
512
+ BridgeAction.Search,
513
+ BridgeAction.Add,
514
+ ],
515
+ ...(source
516
+ ? {
517
+ source,
518
+ }
519
+ : {}),
520
+ },
521
+ ],
522
+ events: [],
523
+ });
524
+
525
+ const workshopSource = (overrides: Partial<BridgeCatalogSource> = {}): BridgeCatalogSource => ({
526
+ kind: BridgeCatalogSourceKind.Workshop,
527
+ appId: 1_623_730,
528
+ provided: [
529
+ "UE4SS",
530
+ "PalSchema",
531
+ ],
532
+ ...overrides,
533
+ });
534
+
535
+ describe("the catalog mirror", () => {
536
+ it("carries a sourced catalog and its section's upload through unchanged", () => {
537
+ const source = catalogManifest(workshopSource(), MODS_UPLOAD);
538
+
539
+ expect(bridgeManifestSchema.parse(source)).toEqual(source);
540
+ });
541
+
542
+ it("takes a source that names nothing the image provides", () => {
543
+ expect(
544
+ bridgeManifestSchema.safeParse(
545
+ catalogManifest({
546
+ kind: BridgeCatalogSourceKind.Workshop,
547
+ appId: 1_623_730,
548
+ }),
549
+ ).success,
550
+ ).toBe(true);
551
+ });
552
+
553
+ it("reads a catalog from an older bridge that carries no source and no upload", () => {
554
+ const parsed = bridgeManifestSchema.parse(catalogManifest());
555
+
556
+ expect(parsed.modules[0]?.source).toBeUndefined();
557
+ expect(parsed.panel?.tabs[0]?.sections[0]).toEqual({
558
+ layout: BridgeLayout.Catalog,
559
+ id: "mods",
560
+ module: "mods",
561
+ });
562
+ });
563
+
564
+ it("refuses a source the platform does not serve", () => {
565
+ const source = catalogManifest(
566
+ workshopSource({
567
+ kind: "nexus" as BridgeCatalogSourceKind,
568
+ }),
569
+ );
570
+
571
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
572
+ });
573
+
574
+ it("refuses an app id that is not a positive whole number", () => {
575
+ for (const appId of [
576
+ 0,
577
+ -1,
578
+ 1.5,
579
+ ]) {
580
+ const source = catalogManifest(
581
+ workshopSource({
582
+ appId,
583
+ }),
584
+ );
585
+
586
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
587
+ }
588
+ });
589
+
590
+ it("refuses an empty provided name", () => {
591
+ const source = catalogManifest(
592
+ workshopSource({
593
+ provided: [
594
+ "",
595
+ ],
596
+ }),
597
+ );
598
+
599
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
600
+ });
601
+ });
602
+
463
603
  const fieldManifest = (field: BridgeField): BridgeManifest => ({
464
604
  version: "1.0.0",
465
605
  panel: {
@@ -521,6 +661,43 @@ describe("secret fields", () => {
521
661
  });
522
662
  });
523
663
 
664
+ describe("field defaults", () => {
665
+ it("carries a field's default through unchanged", () => {
666
+ const source = fieldManifest({
667
+ key: "WINDOWS_STEAM",
668
+ control: BridgeControl.Boolean,
669
+ label: text("Use Steam"),
670
+ default: "true",
671
+ });
672
+
673
+ expect(bridgeManifestSchema.parse(source)).toEqual(source);
674
+ });
675
+
676
+ it("reads a field from an older bridge that declares no default", () => {
677
+ const parsed = bridgeManifestSchema.parse(
678
+ fieldManifest({
679
+ key: "WINDOWS_STEAM",
680
+ control: BridgeControl.Boolean,
681
+ label: text("Use Steam"),
682
+ }),
683
+ );
684
+ const [section] = parsed.panel?.tabs[0]?.sections ?? [];
685
+
686
+ expect(section?.layout === BridgeLayout.Form ? section.fields[0]?.default : null).toBeUndefined();
687
+ });
688
+
689
+ it("refuses a default longer than the platform takes", () => {
690
+ const source = fieldManifest({
691
+ key: "MOTD",
692
+ control: BridgeControl.Text,
693
+ label: text("Message"),
694
+ default: "a".repeat(257),
695
+ });
696
+
697
+ expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
698
+ });
699
+ });
700
+
524
701
  const setupManifest = (setup: BridgeSetupManifest | null): BridgeManifest => ({
525
702
  version: "1.0.0",
526
703
  panel: null,
package/src/wire/panel.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import { guideOpenTargetSchema } from "@serverkgg/bridge/guides";
2
2
  import {
3
+ BRIDGE_CATALOG_PROVIDED_LIMIT,
4
+ BRIDGE_DEFAULT_LENGTH_LIMIT,
3
5
  BRIDGE_ID_PATTERN,
4
6
  BRIDGE_MODULE_ID_PATTERN,
5
7
  BRIDGE_PATTERN_LENGTH_LIMIT,
8
+ BRIDGE_PROVIDED_NAME_LENGTH_LIMIT,
6
9
  BridgeAccess,
10
+ BridgeCatalogSourceKind,
7
11
  BridgeConfirm,
8
12
  BridgeControl,
9
13
  BridgeFormTarget,
@@ -120,6 +124,7 @@ export const bridgeFieldSchema = z.object({
120
124
  help: panelTextSchema.optional(),
121
125
  warning: panelTextSchema.optional(),
122
126
  placeholder: z.string().max(256).optional(),
127
+ default: z.string().max(BRIDGE_DEFAULT_LENGTH_LIMIT).optional(),
123
128
  min: z.number().optional(),
124
129
  max: z.number().optional(),
125
130
  step: z.number().optional(),
@@ -263,6 +268,7 @@ export const bridgeCatalogSectionSchema = z.object({
263
268
  variable: zEnvKey.optional(),
264
269
  confirm: z.enum(BridgeConfirm).optional(),
265
270
  confirmText: panelTextSchema.optional(),
271
+ upload: bridgeSectionUploadSchema.optional(),
266
272
  empty: panelTextSchema.optional(),
267
273
  visibleWhen: bridgeVisibleWhenSchema.optional(),
268
274
  access: z.enum(BridgeAccess).optional(),
@@ -352,6 +358,15 @@ export const bridgeSetupManifestSchema: z.ZodType<BridgeSetupManifest> = z.objec
352
358
  steps: z.array(bridgeSetupStepSchema).min(1).max(SETUP_STEPS_LIMIT),
353
359
  });
354
360
 
361
+ export const bridgeCatalogSourceSchema = z.object({
362
+ kind: z.enum(BridgeCatalogSourceKind),
363
+ appId: z.int().positive(),
364
+ provided: z
365
+ .array(z.string().min(1).max(BRIDGE_PROVIDED_NAME_LENGTH_LIMIT))
366
+ .max(BRIDGE_CATALOG_PROVIDED_LIMIT)
367
+ .optional(),
368
+ });
369
+
355
370
  export const bridgeModuleManifestSchema = z.object({
356
371
  id: zBridgeModuleId,
357
372
  kind: z.enum(BridgeKind),
@@ -361,6 +376,7 @@ export const bridgeModuleManifestSchema = z.object({
361
376
  dependsOn: z.array(zKey).max(8),
362
377
  actions: z.array(z.string().min(1).max(64)).max(16),
363
378
  protectedActions: z.array(z.string().min(1).max(64)).max(16).optional(),
379
+ source: bridgeCatalogSourceSchema.optional(),
364
380
  });
365
381
 
366
382
  export const bridgeManifestSchema: z.ZodType<BridgeManifest> = z.object({