@serverkgg/bridge 0.1.2 → 0.1.4

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 (94) hide show
  1. package/package.json +3 -2
  2. package/rules/package.md +65 -0
  3. package/src/blog/articleFrontmatter.test.ts +10 -10
  4. package/src/blog/compile.test.ts +9 -9
  5. package/src/bot/botDeploy.test.ts +16 -16
  6. package/src/bot/botEnvironment.test.ts +8 -8
  7. package/src/bot/botIdentity.test.ts +12 -12
  8. package/src/bot/botPanel.test.ts +13 -13
  9. package/src/bot/botPanel.ts +4 -4
  10. package/src/bot/botState.test.ts +3 -3
  11. package/src/bot/botTerminal.test.ts +5 -5
  12. package/src/bot/botVariables.test.ts +8 -8
  13. package/src/bot/detect/botToken.test.ts +6 -6
  14. package/src/bot/detect/detect.test.ts +21 -21
  15. package/src/bot/detect/detectTable.test.ts +5 -5
  16. package/src/bot/detect/envExample.test.ts +2 -2
  17. package/src/bot/detect/envScan.test.ts +4 -4
  18. package/src/bot/detect/inferBotPlatform.test.ts +4 -4
  19. package/src/bot/detect/keys.test.ts +3 -3
  20. package/src/bot/detect/sensitiveKey.test.ts +5 -5
  21. package/src/bot/detect/serverkJson.test.ts +5 -5
  22. package/src/catalogs/curseforge.test.ts +35 -35
  23. package/src/catalogs/thunderstore.test.ts +9 -9
  24. package/src/cli/detectTable.test.ts +3 -3
  25. package/src/cli/link.test.ts +11 -11
  26. package/src/cli/main.ts +8 -0
  27. package/src/cli/rules.test.ts +198 -0
  28. package/src/cli/rules.ts +188 -0
  29. package/src/cli/validate.test.ts +179 -31
  30. package/src/cli/validate.ts +44 -0
  31. package/src/codecs/ini.test.ts +13 -13
  32. package/src/codecs/javaProperties.test.ts +11 -11
  33. package/src/codecs/sourceCfg.test.ts +14 -14
  34. package/src/codecs/xmlProperties.test.ts +10 -10
  35. package/src/core/bridgePort.test.ts +6 -6
  36. package/src/core/bridgeUserError.test.ts +4 -4
  37. package/src/core/bridgeValue.test.ts +5 -5
  38. package/src/core/describeDownloadFailure.test.ts +12 -12
  39. package/src/core/describeDownloadFailure.ts +10 -10
  40. package/src/guides/markdown.test.ts +31 -31
  41. package/src/install/installStamp.test.ts +9 -9
  42. package/src/manifest/extension.ts +36 -0
  43. package/src/manifest/index.ts +1 -0
  44. package/src/manifest/manifest.test.ts +208 -90
  45. package/src/manifest/manifest.ts +34 -0
  46. package/src/net/loopback.test.ts +10 -10
  47. package/src/presence/rosterSync.test.ts +7 -7
  48. package/src/probes/a2s.test.ts +5 -5
  49. package/src/probes/minecraftPing.test.ts +17 -17
  50. package/src/probes/raknetPing.test.ts +13 -13
  51. package/src/protocol/bridgePreview.ts +1 -0
  52. package/src/rcon/rconAccess.test.ts +20 -20
  53. package/src/rcon/rconAccess.ts +14 -14
  54. package/src/rcon/rconClient.test.ts +49 -49
  55. package/src/rcon/rconFrame.test.ts +14 -14
  56. package/src/rcon/rconIds.test.ts +4 -4
  57. package/src/rcon/rconWriter.test.ts +6 -6
  58. package/src/runtime/bridgeActivity.test.ts +15 -15
  59. package/src/runtime/bridgeBackupLease.test.ts +9 -9
  60. package/src/runtime/bridgeConfigReader.test.ts +5 -5
  61. package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
  62. package/src/runtime/bridgeContext.test.ts +5 -5
  63. package/src/runtime/bridgeDriver.test.ts +94 -94
  64. package/src/runtime/bridgeEvents.test.ts +15 -15
  65. package/src/runtime/bridgeFiles.test.ts +54 -54
  66. package/src/runtime/bridgeInstall.test.ts +10 -10
  67. package/src/runtime/bridgeLiveConfig.test.ts +6 -6
  68. package/src/runtime/bridgePending.test.ts +6 -6
  69. package/src/runtime/bridgeRedaction.test.ts +16 -16
  70. package/src/runtime/bridgeRegistry.test.ts +84 -52
  71. package/src/runtime/bridgeRegistry.ts +2 -2
  72. package/src/runtime/bridgeServer.test.ts +31 -31
  73. package/src/runtime/bridgeSetup.test.ts +27 -27
  74. package/src/runtime/bridgeSetup.ts +1 -1
  75. package/src/runtime/bridgeSetupHandlers.test.ts +13 -13
  76. package/src/runtime/bridgeSignal.test.ts +7 -7
  77. package/src/runtime/bridgeSupervisor.test.ts +42 -42
  78. package/src/runtime/serve.ts +4 -4
  79. package/src/steam/avatars.test.ts +13 -13
  80. package/src/steam/steamApp.test.ts +8 -8
  81. package/src/steam/steamcmd.test.ts +17 -17
  82. package/src/steam/steamcmd.ts +4 -4
  83. package/src/steam/steamcmdNarrator.test.ts +4 -4
  84. package/src/steam/workshop.test.ts +5 -5
  85. package/src/utils/changedSettingKeys.test.ts +5 -5
  86. package/src/utils/describeExecFailure.test.ts +4 -4
  87. package/src/utils/describeExecFailure.ts +1 -1
  88. package/src/utils/sameSettingValue.test.ts +7 -7
  89. package/src/utils/token.test.ts +7 -7
  90. package/src/wine/protonCommand.test.ts +5 -5
  91. package/src/wine/wineCommand.test.ts +6 -6
  92. package/src/wine/winePrefix.test.ts +6 -6
  93. package/src/wire/activity.test.ts +8 -8
  94. package/src/wire/panel.test.ts +28 -28
@@ -1,7 +1,8 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import { PORT_BLOCK_MAX_COUNT, PortJoin, PortProtocol } from "../core";
4
4
  import { GameCategory } from "./category";
5
+ import { ManifestExtensionKind, ManifestExtensionSource } from "./extension";
5
6
  import { buildJsonSchema } from "./jsonSchema";
6
7
  import { PackageKind } from "./kind";
7
8
  import {
@@ -174,7 +175,7 @@ const withPortActiveWhen = (activeWhen: unknown) => {
174
175
  };
175
176
 
176
177
  describe("defaults", () => {
177
- test("fills in the optional sections so consumers never guard", () => {
178
+ it("fills in the optional sections so consumers never guard", () => {
178
179
  const manifest = manifestSchema.parse(base());
179
180
 
180
181
  expect(manifest.backup).toEqual({
@@ -195,11 +196,127 @@ describe("defaults", () => {
195
196
  expect(manifest.container.ports[0]?.hint).toBeUndefined();
196
197
  expect(manifest.container.ports[0]?.join).toBe(PortJoin.Combined);
197
198
  expect(manifest.container.ports[0]?.activeWhen).toEqual([]);
199
+ expect(manifest.metadata.extensions).toEqual([]);
200
+ });
201
+ });
202
+
203
+ describe("extensions", () => {
204
+ const withExtensions = (extensions: unknown) => {
205
+ const manifest = base();
206
+
207
+ return {
208
+ ...manifest,
209
+ metadata: {
210
+ ...manifest.metadata,
211
+ extensions,
212
+ },
213
+ };
214
+ };
215
+
216
+ it("carry each kind with its catalogs, and no catalog when it is added by hand", () => {
217
+ const manifest = manifestSchema.parse(
218
+ withExtensions([
219
+ {
220
+ kind: ManifestExtensionKind.Mods,
221
+ sources: [
222
+ ManifestExtensionSource.Modrinth,
223
+ ManifestExtensionSource.CurseForge,
224
+ ],
225
+ },
226
+ {
227
+ kind: ManifestExtensionKind.Plugins,
228
+ },
229
+ ]),
230
+ );
231
+
232
+ expect(manifest.metadata.extensions).toEqual([
233
+ {
234
+ kind: ManifestExtensionKind.Mods,
235
+ sources: [
236
+ ManifestExtensionSource.Modrinth,
237
+ ManifestExtensionSource.CurseForge,
238
+ ],
239
+ },
240
+ {
241
+ kind: ManifestExtensionKind.Plugins,
242
+ sources: [],
243
+ },
244
+ ]);
245
+ });
246
+
247
+ it("refuse a kind declared twice", () => {
248
+ expect(
249
+ failure(
250
+ withExtensions([
251
+ {
252
+ kind: ManifestExtensionKind.Mods,
253
+ },
254
+ {
255
+ kind: ManifestExtensionKind.Mods,
256
+ sources: [
257
+ ManifestExtensionSource.CurseForge,
258
+ ],
259
+ },
260
+ ]),
261
+ ),
262
+ ).toEqual([
263
+ {
264
+ path: "metadata.extensions.1.kind",
265
+ message: 'extension "mods" is declared twice',
266
+ },
267
+ ]);
268
+ });
269
+
270
+ it("refuse a catalog listed twice for one kind", () => {
271
+ expect(
272
+ failure(
273
+ withExtensions([
274
+ {
275
+ kind: ManifestExtensionKind.Mods,
276
+ sources: [
277
+ ManifestExtensionSource.Modrinth,
278
+ ManifestExtensionSource.Modrinth,
279
+ ],
280
+ },
281
+ ]),
282
+ ),
283
+ ).toEqual([
284
+ {
285
+ path: "metadata.extensions.0.sources.1",
286
+ message: 'source "modrinth" is listed twice for mods',
287
+ },
288
+ ]);
289
+ });
290
+
291
+ it("refuse a catalog the platform does not know", () => {
292
+ expect(
293
+ manifestSchema.safeParse(
294
+ withExtensions([
295
+ {
296
+ kind: ManifestExtensionKind.Mods,
297
+ sources: [
298
+ "steam-workshop",
299
+ ],
300
+ },
301
+ ]),
302
+ ).success,
303
+ ).toBe(false);
304
+ });
305
+
306
+ it("render into the JSON schema", () => {
307
+ const rendered = JSON.stringify(buildJsonSchema());
308
+
309
+ for (const value of [
310
+ ...Object.values(ManifestExtensionKind),
311
+ ...Object.values(ManifestExtensionSource),
312
+ ]) {
313
+ expect(rendered).toContain(`"${value}"`);
314
+ }
198
315
  });
199
316
  });
200
317
 
201
318
  describe("port presentation", () => {
202
- test("keeps the localized name, hint and join style a port declares", () => {
319
+ it("keeps the localized name, hint and join style a port declares", () => {
203
320
  const manifest = manifestSchema.parse(
204
321
  withPort({
205
322
  name: {
@@ -222,7 +339,7 @@ describe("port presentation", () => {
222
339
  expect(manifest.container.ports[0]?.join).toBe(PortJoin.Separate);
223
340
  });
224
341
 
225
- test("rejects a port name missing one language", () => {
342
+ it("rejects a port name missing one language", () => {
226
343
  expect(
227
344
  failure(
228
345
  withPort({
@@ -241,7 +358,7 @@ describe("port presentation", () => {
241
358
  });
242
359
 
243
360
  describe("port srv", () => {
244
- test("keeps the service label a port declares", () => {
361
+ it("keeps the service label a port declares", () => {
245
362
  const manifest = manifestSchema.parse(
246
363
  withPortSrv({
247
364
  service: "_minecraft",
@@ -253,7 +370,7 @@ describe("port srv", () => {
253
370
  });
254
371
  });
255
372
 
256
- test("rejects a service label without its underscore", () => {
373
+ it("rejects a service label without its underscore", () => {
257
374
  expect(
258
375
  failure(
259
376
  withPortSrv({
@@ -270,7 +387,7 @@ describe("port srv", () => {
270
387
  });
271
388
 
272
389
  describe("port activeWhen", () => {
273
- test("keeps every condition a port declares", () => {
390
+ it("keeps every condition a port declares", () => {
274
391
  const manifest = manifestSchema.parse(
275
392
  withPortActiveWhen([
276
393
  {
@@ -306,7 +423,7 @@ describe("port activeWhen", () => {
306
423
  ]);
307
424
  });
308
425
 
309
- test("rejects a condition that declares neither values nor empty", () => {
426
+ it("rejects a condition that declares neither values nor empty", () => {
310
427
  expect(
311
428
  failure(
312
429
  withPortActiveWhen([
@@ -323,7 +440,7 @@ describe("port activeWhen", () => {
323
440
  ]);
324
441
  });
325
442
 
326
- test("rejects a condition whose values list is empty", () => {
443
+ it("rejects a condition whose values list is empty", () => {
327
444
  expect(
328
445
  failure(
329
446
  withPortActiveWhen([
@@ -341,7 +458,7 @@ describe("port activeWhen", () => {
341
458
  ]);
342
459
  });
343
460
 
344
- test("rejects a fifth condition", () => {
461
+ it("rejects a fifth condition", () => {
345
462
  expect(
346
463
  failure(
347
464
  withPortActiveWhen(
@@ -361,7 +478,7 @@ describe("port activeWhen", () => {
361
478
  ]);
362
479
  });
363
480
 
364
- test("rejects a variable that is not an environment key", () => {
481
+ it("rejects a variable that is not an environment key", () => {
365
482
  expect(
366
483
  failure(
367
484
  withPortActiveWhen([
@@ -381,7 +498,7 @@ describe("port activeWhen", () => {
381
498
  });
382
499
 
383
500
  describe("cross-entry checks", () => {
384
- test("rejects an image that does not match metadata.id", () => {
501
+ it("rejects an image that does not match metadata.id", () => {
385
502
  const manifest = base();
386
503
 
387
504
  manifest.container.image = "serverkgg/minecraft-java";
@@ -394,7 +511,7 @@ describe("cross-entry checks", () => {
394
511
  ]);
395
512
  });
396
513
 
397
- test("rejects duplicate secret keys", () => {
514
+ it("rejects duplicate secret keys", () => {
398
515
  const manifest = {
399
516
  ...base(),
400
517
  secrets: [
@@ -417,7 +534,7 @@ describe("cross-entry checks", () => {
417
534
  ]);
418
535
  });
419
536
 
420
- test("rejects duplicate protected paths and notable matches", () => {
537
+ it("rejects duplicate protected paths and notable matches", () => {
421
538
  const manifest = {
422
539
  ...base(),
423
540
  files: {
@@ -452,7 +569,7 @@ describe("cross-entry checks", () => {
452
569
  ]);
453
570
  });
454
571
 
455
- test("rejects duplicate port keys and overlapping host ranges", () => {
572
+ it("rejects duplicate port keys and overlapping host ranges", () => {
456
573
  const manifest = base();
457
574
  const [game] = manifest.container.ports;
458
575
 
@@ -481,7 +598,7 @@ describe("cross-entry checks", () => {
481
598
  ]);
482
599
  });
483
600
 
484
- test("rejects a guide tag that is not declared", () => {
601
+ it("rejects a guide tag that is not declared", () => {
485
602
  const manifest = {
486
603
  ...base(),
487
604
  guides: [
@@ -508,7 +625,7 @@ describe("cross-entry checks", () => {
508
625
  ]);
509
626
  });
510
627
 
511
- test("rejects one body file serving both locales", () => {
628
+ it("rejects one body file serving both locales", () => {
512
629
  const manifest = {
513
630
  ...base(),
514
631
  guides: [
@@ -534,7 +651,7 @@ describe("cross-entry checks", () => {
534
651
  });
535
652
 
536
653
  describe("field validation", () => {
537
- test("explains a bad host range instead of saying nothing", () => {
654
+ it("explains a bad host range instead of saying nothing", () => {
538
655
  const manifest = base();
539
656
  const [game] = manifest.container.ports;
540
657
 
@@ -556,7 +673,7 @@ describe("field validation", () => {
556
673
  ]);
557
674
  });
558
675
 
559
- test("explains a default that sits outside its range", () => {
676
+ it("explains a default that sits outside its range", () => {
560
677
  const manifest = base();
561
678
  const [game] = manifest.container.ports;
562
679
 
@@ -574,7 +691,7 @@ describe("field validation", () => {
574
691
  ]);
575
692
  });
576
693
 
577
- test("explains a bad colour", () => {
694
+ it("explains a bad colour", () => {
578
695
  const manifest = {
579
696
  ...base(),
580
697
  files: {
@@ -597,7 +714,7 @@ describe("field validation", () => {
597
714
  ]);
598
715
  });
599
716
 
600
- test("field failures short-circuit the cross-entry checks", () => {
717
+ it("field failures short-circuit the cross-entry checks", () => {
601
718
  const manifest = base();
602
719
 
603
720
  manifest.metadata.id = "Minecraft";
@@ -615,7 +732,7 @@ describe("field validation", () => {
615
732
  });
616
733
 
617
734
  describe("packageSchema", () => {
618
- test("routes on kind without a separate pre-parse", () => {
735
+ it("routes on kind without a separate pre-parse", () => {
619
736
  const game = packageSchema.parse(base());
620
737
 
621
738
  expect(game.kind).toBe(PackageKind.Game);
@@ -642,7 +759,7 @@ describe("packageSchema", () => {
642
759
  expect(guides.kind).toBe(PackageKind.Guide);
643
760
  });
644
761
 
645
- test("guide packages get the same guide checks", () => {
762
+ it("guide packages get the same guide checks", () => {
646
763
  const result = guidePackageSchema.safeParse({
647
764
  schemaVersion: MANIFEST_SCHEMA_VERSION,
648
765
  kind: "GuidePackage",
@@ -669,7 +786,7 @@ describe("packageSchema", () => {
669
786
  expect(result.error && z.prettifyError(result.error)).toContain('tag "start" is not declared in guideTags');
670
787
  });
671
788
 
672
- test("carries up to 64 guides", () => {
789
+ it("carries up to 64 guides", () => {
673
790
  const many = (count: number) =>
674
791
  Array.from(
675
792
  {
@@ -684,14 +801,14 @@ describe("packageSchema", () => {
684
801
  });
685
802
 
686
803
  describe("presence", () => {
687
- test("is optional, so a game that declares none parses", () => {
804
+ it("is optional, so a game that declares none parses", () => {
688
805
  const result = manifestSchema.safeParse(base());
689
806
 
690
807
  expect(result.success).toBe(true);
691
808
  expect(result.success && result.data.presence).toBeUndefined();
692
809
  });
693
810
 
694
- test("defaults the name key to player and the field list to empty", () => {
811
+ it("defaults the name key to player and the field list to empty", () => {
695
812
  const result = manifestSchema.safeParse({
696
813
  ...base(),
697
814
  presence: {},
@@ -702,7 +819,7 @@ describe("presence", () => {
702
819
  expect(result.success && result.data.presence?.fields).toEqual([]);
703
820
  });
704
821
 
705
- test("accepts an avatar template and localized fields", () => {
822
+ it("accepts an avatar template and localized fields", () => {
706
823
  const result = manifestSchema.safeParse({
707
824
  ...base(),
708
825
  presence: {
@@ -722,7 +839,7 @@ describe("presence", () => {
722
839
  expect(result.success && result.data.presence?.fields[0]?.inline).toBe(true);
723
840
  });
724
841
 
725
- test("accepts a serverk avatar template", () => {
842
+ it("accepts a serverk avatar template", () => {
726
843
  const result = manifestSchema.safeParse({
727
844
  ...base(),
728
845
  presence: {
@@ -736,7 +853,7 @@ describe("presence", () => {
736
853
  expect(result.success && result.data.presence?.avatar).toBe("serverk://avatars/hytale/{playerId}");
737
854
  });
738
855
 
739
- test("refuses an avatar template that is neither https nor a serverk avatar", () => {
856
+ it("refuses an avatar template that is neither https nor a serverk avatar", () => {
740
857
  const result = manifestSchema.safeParse({
741
858
  ...base(),
742
859
  presence: {
@@ -750,7 +867,7 @@ describe("presence", () => {
750
867
  );
751
868
  });
752
869
 
753
- test("refuses a serverk scheme that is not an avatar path", () => {
870
+ it("refuses a serverk scheme that is not an avatar path", () => {
754
871
  const result = manifestSchema.safeParse({
755
872
  ...base(),
756
873
  presence: {
@@ -764,7 +881,7 @@ describe("presence", () => {
764
881
  );
765
882
  });
766
883
 
767
- test("refuses a serverk avatar template longer than the limit", () => {
884
+ it("refuses a serverk avatar template longer than the limit", () => {
768
885
  const result = manifestSchema.safeParse({
769
886
  ...base(),
770
887
  presence: {
@@ -775,12 +892,12 @@ describe("presence", () => {
775
892
  expect(result.success).toBe(false);
776
893
  });
777
894
 
778
- test("tells a serverk avatar template apart from an https one", () => {
895
+ it("tells a serverk avatar template apart from an https one", () => {
779
896
  expect(isServerkAvatarTemplate("serverk://avatars/hytale/{playerId}")).toBe(true);
780
897
  expect(isServerkAvatarTemplate("https://mc-heads.net/avatar/{skin}/128")).toBe(false);
781
898
  });
782
899
 
783
- test("refuses a payload key that could never be a capture group", () => {
900
+ it("refuses a payload key that could never be a capture group", () => {
784
901
  const result = manifestSchema.safeParse({
785
902
  ...base(),
786
903
  presence: {
@@ -792,7 +909,7 @@ describe("presence", () => {
792
909
  expect(result.error && z.prettifyError(result.error)).toContain("must be a valid capture group name");
793
910
  });
794
911
 
795
- test("refuses the same field declared twice", () => {
912
+ it("refuses the same field declared twice", () => {
796
913
  const result = manifestSchema.safeParse({
797
914
  ...base(),
798
915
  presence: {
@@ -815,7 +932,7 @@ describe("presence", () => {
815
932
  });
816
933
 
817
934
  describe("container runtime", () => {
818
- test("materializes the defaults when the manifest declares no runtime", () => {
935
+ it("materializes the defaults when the manifest declares no runtime", () => {
819
936
  const manifest = manifestSchema.parse(base());
820
937
 
821
938
  expect(manifest.container.runtime).toEqual({
@@ -829,7 +946,7 @@ describe("container runtime", () => {
829
946
  expect(MANIFEST_RUNTIME_DEFAULTS).toEqual(manifest.container.runtime);
830
947
  });
831
948
 
832
- test("keeps every field a manifest declares", () => {
949
+ it("keeps every field a manifest declares", () => {
833
950
  const manifest = manifestSchema.parse(
834
951
  withRuntime({
835
952
  platform: ManifestPlatform.Wine,
@@ -857,7 +974,7 @@ describe("container runtime", () => {
857
974
  });
858
975
  });
859
976
 
860
- test("clamps an install budget above the ceiling", () => {
977
+ it("clamps an install budget above the ceiling", () => {
861
978
  expect(
862
979
  failure(
863
980
  withRuntime({
@@ -872,7 +989,7 @@ describe("container runtime", () => {
872
989
  ]);
873
990
  });
874
991
 
875
- test("clamps a boot budget below the floor", () => {
992
+ it("clamps a boot budget below the floor", () => {
876
993
  expect(
877
994
  failure(
878
995
  withRuntime({
@@ -887,7 +1004,7 @@ describe("container runtime", () => {
887
1004
  ]);
888
1005
  });
889
1006
 
890
- test("clamps shared memory and the pids limit to their range", () => {
1007
+ it("clamps shared memory and the pids limit to their range", () => {
891
1008
  expect(
892
1009
  failure(
893
1010
  withRuntime({
@@ -907,7 +1024,7 @@ describe("container runtime", () => {
907
1024
  ]);
908
1025
  });
909
1026
 
910
- test("refuses a fourth capability", () => {
1027
+ it("refuses a fourth capability", () => {
911
1028
  expect(
912
1029
  failure(
913
1030
  withRuntime({
@@ -927,7 +1044,7 @@ describe("container runtime", () => {
927
1044
  ]);
928
1045
  });
929
1046
 
930
- test("refuses a capability outside the allow-list", () => {
1047
+ it("refuses a capability outside the allow-list", () => {
931
1048
  expect(
932
1049
  failure(
933
1050
  withRuntime({
@@ -944,7 +1061,7 @@ describe("container runtime", () => {
944
1061
  ]);
945
1062
  });
946
1063
 
947
- test("refuses a wine platform running on the default shared memory", () => {
1064
+ it("refuses a wine platform running on the default shared memory", () => {
948
1065
  expect(
949
1066
  failure(
950
1067
  withRuntime({
@@ -959,7 +1076,7 @@ describe("container runtime", () => {
959
1076
  ]);
960
1077
  });
961
1078
 
962
- test("accepts a wine platform once shared memory clears the floor", () => {
1079
+ it("accepts a wine platform once shared memory clears the floor", () => {
963
1080
  const manifest = manifestSchema.parse(
964
1081
  withRuntime({
965
1082
  platform: ManifestPlatform.Wine,
@@ -972,7 +1089,7 @@ describe("container runtime", () => {
972
1089
  });
973
1090
 
974
1091
  describe("manifestRuntimeOf", () => {
975
- test("reads the runtime a manifest declares", () => {
1092
+ it("reads the runtime a manifest declares", () => {
976
1093
  const manifest = manifestSchema.parse(
977
1094
  withRuntime({
978
1095
  platform: ManifestPlatform.Wine,
@@ -987,7 +1104,7 @@ describe("manifestRuntimeOf", () => {
987
1104
  });
988
1105
  });
989
1106
 
990
- test("falls back to the defaults for a bundle published before the field existed", () => {
1107
+ it("falls back to the defaults for a bundle published before the field existed", () => {
991
1108
  const manifest = manifestSchema.parse(base());
992
1109
  const legacy = {
993
1110
  ...manifest,
@@ -1002,14 +1119,14 @@ describe("manifestRuntimeOf", () => {
1002
1119
  });
1003
1120
 
1004
1121
  describe("port mirroring", () => {
1005
- test("defaults to no mirror and no follower", () => {
1122
+ it("defaults to no mirror and no follower", () => {
1006
1123
  const manifest = manifestSchema.parse(base());
1007
1124
 
1008
1125
  expect(manifest.container.ports[0]?.mirror).toBe(false);
1009
1126
  expect(manifest.container.ports[0]?.follows).toBeUndefined();
1010
1127
  });
1011
1128
 
1012
- test("keeps the mirror flag and the follow rule a manifest declares", () => {
1129
+ it("keeps the mirror flag and the follow rule a manifest declares", () => {
1013
1130
  const manifest = manifestSchema.parse(
1014
1131
  withPorts([
1015
1132
  udpPort("game", 27_015, 27_015),
@@ -1030,7 +1147,7 @@ describe("port mirroring", () => {
1030
1147
  });
1031
1148
  });
1032
1149
 
1033
- test("refuses a follow rule naming a port that is not declared", () => {
1150
+ it("refuses a follow rule naming a port that is not declared", () => {
1034
1151
  expect(
1035
1152
  failure(
1036
1153
  withPorts([
@@ -1051,7 +1168,7 @@ describe("port mirroring", () => {
1051
1168
  ]);
1052
1169
  });
1053
1170
 
1054
- test("refuses a port that follows itself", () => {
1171
+ it("refuses a port that follows itself", () => {
1055
1172
  expect(
1056
1173
  failure(
1057
1174
  withPorts([
@@ -1072,7 +1189,7 @@ describe("port mirroring", () => {
1072
1189
  ]);
1073
1190
  });
1074
1191
 
1075
- test("refuses a follow rule crossing protocols", () => {
1192
+ it("refuses a follow rule crossing protocols", () => {
1076
1193
  const [tcp] = base().container.ports;
1077
1194
 
1078
1195
  if (!tcp) {
@@ -1100,7 +1217,7 @@ describe("port mirroring", () => {
1100
1217
  ]);
1101
1218
  });
1102
1219
 
1103
- test("refuses a port that both follows and is followed", () => {
1220
+ it("refuses a port that both follows and is followed", () => {
1104
1221
  expect(
1105
1222
  failure(
1106
1223
  withPorts([
@@ -1129,7 +1246,7 @@ describe("port mirroring", () => {
1129
1246
  ]);
1130
1247
  });
1131
1248
 
1132
- test("lets a follower's host range overlap the port it follows", () => {
1249
+ it("lets a follower's host range overlap the port it follows", () => {
1133
1250
  const manifest = manifestSchema.parse(
1134
1251
  withPorts([
1135
1252
  udpPort("game", 27_015, 27_015),
@@ -1146,7 +1263,7 @@ describe("port mirroring", () => {
1146
1263
  expect(manifest.container.ports[1]?.hostRange.min).toBe(27_016);
1147
1264
  });
1148
1265
 
1149
- test("still refuses two independent ports whose host ranges overlap", () => {
1266
+ it("still refuses two independent ports whose host ranges overlap", () => {
1150
1267
  expect(
1151
1268
  failure(
1152
1269
  withPorts([
@@ -1162,7 +1279,7 @@ describe("port mirroring", () => {
1162
1279
  ]);
1163
1280
  });
1164
1281
 
1165
- test("refuses an offset outside its range", () => {
1282
+ it("refuses an offset outside its range", () => {
1166
1283
  expect(
1167
1284
  failure(
1168
1285
  withPorts([
@@ -1204,13 +1321,13 @@ describe("port pairs and blocks", () => {
1204
1321
  },
1205
1322
  });
1206
1323
 
1207
- test("defaults a port to a single binding", () => {
1324
+ it("defaults a port to a single binding", () => {
1208
1325
  const manifest = manifestSchema.parse(base());
1209
1326
 
1210
1327
  expect(manifest.container.ports[0]?.count).toBe(1);
1211
1328
  });
1212
1329
 
1213
- test("accepts a tcp+udp join port beside a udp media block", () => {
1330
+ it("accepts a tcp+udp join port beside a udp media block", () => {
1214
1331
  const manifest = manifestSchema.parse(
1215
1332
  withPorts([
1216
1333
  pairPort("game", 19_132),
@@ -1235,7 +1352,7 @@ describe("port pairs and blocks", () => {
1235
1352
  ]);
1236
1353
  });
1237
1354
 
1238
- test("refuses a count above the block limit", () => {
1355
+ it("refuses a count above the block limit", () => {
1239
1356
  expect(
1240
1357
  failure(
1241
1358
  withPorts([
@@ -1250,7 +1367,7 @@ describe("port pairs and blocks", () => {
1250
1367
  ]);
1251
1368
  });
1252
1369
 
1253
- test("refuses a block whose host range cannot hold it", () => {
1370
+ it("refuses a block whose host range cannot hold it", () => {
1254
1371
  expect(
1255
1372
  failure(
1256
1373
  withPorts([
@@ -1281,7 +1398,7 @@ describe("port pairs and blocks", () => {
1281
1398
  },
1282
1399
  });
1283
1400
 
1284
- test("lets a single port span the whole cap", () => {
1401
+ it("lets a single port span the whole cap", () => {
1285
1402
  const manifest = manifestSchema.parse(
1286
1403
  withPorts([
1287
1404
  spanning("game", 20_000, 20_999, 1),
@@ -1291,7 +1408,7 @@ describe("port pairs and blocks", () => {
1291
1408
  expect(manifest.container.ports[0]?.hostRange.max).toBe(20_999);
1292
1409
  });
1293
1410
 
1294
- test("refuses a single port spanning one more than the cap", () => {
1411
+ it("refuses a single port spanning one more than the cap", () => {
1295
1412
  expect(
1296
1413
  failure(
1297
1414
  withPorts([
@@ -1306,7 +1423,7 @@ describe("port pairs and blocks", () => {
1306
1423
  ]);
1307
1424
  });
1308
1425
 
1309
- test("counts the span of a block in slots, so 100 slots of 64 pass", () => {
1426
+ it("counts the span of a block in slots, so 100 slots of 64 pass", () => {
1310
1427
  const manifest = manifestSchema.parse(
1311
1428
  withPorts([
1312
1429
  spanning("media", 20_000, 26_399, 64),
@@ -1316,7 +1433,7 @@ describe("port pairs and blocks", () => {
1316
1433
  expect(manifest.container.ports[0]?.count).toBe(64);
1317
1434
  });
1318
1435
 
1319
- test("refuses a block spanning more slots than the cap", () => {
1436
+ it("refuses a block spanning more slots than the cap", () => {
1320
1437
  expect(
1321
1438
  failure(
1322
1439
  withPorts([
@@ -1331,7 +1448,7 @@ describe("port pairs and blocks", () => {
1331
1448
  ]);
1332
1449
  });
1333
1450
 
1334
- test("refuses a block whose default leaves no room for the whole block", () => {
1451
+ it("refuses a block whose default leaves no room for the whole block", () => {
1335
1452
  expect(
1336
1453
  failure(
1337
1454
  withPorts([
@@ -1353,7 +1470,7 @@ describe("port pairs and blocks", () => {
1353
1470
  ]);
1354
1471
  });
1355
1472
 
1356
- test("refuses a non-mirrored block that runs past the last port", () => {
1473
+ it("refuses a non-mirrored block that runs past the last port", () => {
1357
1474
  expect(
1358
1475
  failure(
1359
1476
  withPorts([
@@ -1372,7 +1489,7 @@ describe("port pairs and blocks", () => {
1372
1489
  ]);
1373
1490
  });
1374
1491
 
1375
- test("refuses the player-address fields on a block", () => {
1492
+ it("refuses the player-address fields on a block", () => {
1376
1493
  expect(
1377
1494
  failure(
1378
1495
  withPorts([
@@ -1407,7 +1524,7 @@ describe("port pairs and blocks", () => {
1407
1524
  ]);
1408
1525
  });
1409
1526
 
1410
- test("refuses a block that follows a port", () => {
1527
+ it("refuses a block that follows a port", () => {
1411
1528
  expect(
1412
1529
  failure(
1413
1530
  withPorts([
@@ -1429,7 +1546,7 @@ describe("port pairs and blocks", () => {
1429
1546
  ]);
1430
1547
  });
1431
1548
 
1432
- test("refuses a port that follows a block", () => {
1549
+ it("refuses a port that follows a block", () => {
1433
1550
  expect(
1434
1551
  failure(
1435
1552
  withPorts([
@@ -1451,7 +1568,7 @@ describe("port pairs and blocks", () => {
1451
1568
  ]);
1452
1569
  });
1453
1570
 
1454
- test("refuses srv on a tcp+udp port", () => {
1571
+ it("refuses srv on a tcp+udp port", () => {
1455
1572
  expect(
1456
1573
  failure(
1457
1574
  withPorts([
@@ -1471,7 +1588,7 @@ describe("port pairs and blocks", () => {
1471
1588
  ]);
1472
1589
  });
1473
1590
 
1474
- test("refuses a tcp+udp port that follows another", () => {
1591
+ it("refuses a tcp+udp port that follows another", () => {
1475
1592
  expect(
1476
1593
  failure(
1477
1594
  withPorts([
@@ -1493,7 +1610,7 @@ describe("port pairs and blocks", () => {
1493
1610
  ]);
1494
1611
  });
1495
1612
 
1496
- test("refuses a port that follows a tcp+udp port", () => {
1613
+ it("refuses a port that follows a tcp+udp port", () => {
1497
1614
  expect(
1498
1615
  failure(
1499
1616
  withPorts([
@@ -1515,7 +1632,7 @@ describe("port pairs and blocks", () => {
1515
1632
  ]);
1516
1633
  });
1517
1634
 
1518
- test("refuses a tcp+udp port overlapping a udp port on the shared protocol", () => {
1635
+ it("refuses a tcp+udp port overlapping a udp port on the shared protocol", () => {
1519
1636
  expect(
1520
1637
  failure(
1521
1638
  withPorts([
@@ -1531,7 +1648,7 @@ describe("port pairs and blocks", () => {
1531
1648
  ]);
1532
1649
  });
1533
1650
 
1534
- test("lets a tcp port and a udp port share a host range", () => {
1651
+ it("lets a tcp port and a udp port share a host range", () => {
1535
1652
  const [tcp] = base().container.ports;
1536
1653
 
1537
1654
  if (!tcp) {
@@ -1548,7 +1665,7 @@ describe("port pairs and blocks", () => {
1548
1665
  expect(manifest.container.ports).toHaveLength(2);
1549
1666
  });
1550
1667
 
1551
- test("refuses a manifest whose pairs expand past the socket port limit", () => {
1668
+ it("refuses a manifest whose pairs expand past the socket port limit", () => {
1552
1669
  expect(
1553
1670
  failure(
1554
1671
  withPorts(
@@ -1625,17 +1742,18 @@ const yamlSource = (document: unknown): PackageSource => {
1625
1742
  };
1626
1743
 
1627
1744
  describe("bot packages", () => {
1628
- test("parse with no ports and no category", () => {
1745
+ it("parse with no ports and no category", () => {
1629
1746
  const manifest = botManifestSchema.parse(botBase());
1630
1747
 
1631
1748
  expect(manifest.kind).toBe(PackageKind.Bot);
1632
1749
  expect(manifest.container.ports).toEqual([]);
1633
1750
  expect("category" in manifest.metadata).toBe(false);
1751
+ expect("extensions" in manifest.metadata).toBe(false);
1634
1752
  expect(manifest.secrets).toBeUndefined();
1635
1753
  expect(manifest.presence).toBeUndefined();
1636
1754
  });
1637
1755
 
1638
- test("default the ports to none when a bot declares no ports key", () => {
1756
+ it("default the ports to none when a bot declares no ports key", () => {
1639
1757
  const { container, ...rest } = botBase();
1640
1758
  const manifest = botManifestSchema.parse({
1641
1759
  ...rest,
@@ -1647,7 +1765,7 @@ describe("bot packages", () => {
1647
1765
  expect(manifest.container.ports).toEqual([]);
1648
1766
  });
1649
1767
 
1650
- test("refuse a secrets list with the environment vault named as the place for per-bot values", () => {
1768
+ it("refuse a secrets list with the environment vault named as the place for per-bot values", () => {
1651
1769
  expect(
1652
1770
  botFailure({
1653
1771
  ...botBase(),
@@ -1661,7 +1779,7 @@ describe("bot packages", () => {
1661
1779
  ]);
1662
1780
  });
1663
1781
 
1664
- test("keep the environment vault off unless the package turns it on", () => {
1782
+ it("keep the environment vault off unless the package turns it on", () => {
1665
1783
  expect(botManifestSchema.parse(botBase()).environment.vault).toBe(false);
1666
1784
  expect(
1667
1785
  botManifestSchema.parse({
@@ -1673,7 +1791,7 @@ describe("bot packages", () => {
1673
1791
  ).toBe(true);
1674
1792
  });
1675
1793
 
1676
- test("refuse a vault flag that is not a boolean", () => {
1794
+ it("refuse a vault flag that is not a boolean", () => {
1677
1795
  expect(
1678
1796
  botFailure({
1679
1797
  ...botBase(),
@@ -1686,7 +1804,7 @@ describe("bot packages", () => {
1686
1804
  ]);
1687
1805
  });
1688
1806
 
1689
- test("refuse a presence block", () => {
1807
+ it("refuse a presence block", () => {
1690
1808
  expect(
1691
1809
  botFailure({
1692
1810
  ...botBase(),
@@ -1702,7 +1820,7 @@ describe("bot packages", () => {
1702
1820
  ]);
1703
1821
  });
1704
1822
 
1705
- test("still run the shared package checks", () => {
1823
+ it("still run the shared package checks", () => {
1706
1824
  expect(
1707
1825
  botFailure({
1708
1826
  ...botBase(),
@@ -1719,7 +1837,7 @@ describe("bot packages", () => {
1719
1837
  ]);
1720
1838
  });
1721
1839
 
1722
- test("keep a port a bot declares for a webhook", () => {
1840
+ it("keep a port a bot declares for a webhook", () => {
1723
1841
  const manifest = botManifestSchema.parse({
1724
1842
  ...botBase(),
1725
1843
  container: {
@@ -1731,7 +1849,7 @@ describe("bot packages", () => {
1731
1849
  expect(manifest.container.ports).toHaveLength(1);
1732
1850
  });
1733
1851
 
1734
- test("reach the runtime reader like a game does", () => {
1852
+ it("reach the runtime reader like a game does", () => {
1735
1853
  expect(manifestRuntimeOf(botManifestSchema.parse(botBase()))).toEqual({
1736
1854
  ...MANIFEST_RUNTIME_DEFAULTS,
1737
1855
  pidsLimit: 256,
@@ -1740,7 +1858,7 @@ describe("bot packages", () => {
1740
1858
  });
1741
1859
 
1742
1860
  describe("package kinds", () => {
1743
- test("route all three kinds through packageSchema", () => {
1861
+ it("route all three kinds through packageSchema", () => {
1744
1862
  expect(packageSchema.parse(base()).kind).toBe(PackageKind.Game);
1745
1863
  expect(packageSchema.parse(botBase()).kind).toBe(PackageKind.Bot);
1746
1864
  expect(
@@ -1752,13 +1870,13 @@ describe("package kinds", () => {
1752
1870
  ).toBe(PackageKind.Guide);
1753
1871
  });
1754
1872
 
1755
- test("keep a game package on the game rules, so it still needs a port", () => {
1873
+ it("keep a game package on the game rules, so it still needs a port", () => {
1756
1874
  const result = packageSchema.safeParse(withPorts([]));
1757
1875
 
1758
1876
  expect(result.success).toBe(false);
1759
1877
  });
1760
1878
 
1761
- test("refuse a guide package where a runnable package is expected", () => {
1879
+ it("refuse a guide package where a runnable package is expected", () => {
1762
1880
  expect(
1763
1881
  runnablePackageSchema.safeParse(
1764
1882
  guidePackage([
@@ -1768,7 +1886,7 @@ describe("package kinds", () => {
1768
1886
  ).toBe(false);
1769
1887
  });
1770
1888
 
1771
- test("tell a game from a bot", () => {
1889
+ it("tell a game from a bot", () => {
1772
1890
  const game = runnablePackageSchema.parse(base());
1773
1891
  const bot = runnablePackageSchema.parse(botBase());
1774
1892
 
@@ -1778,24 +1896,24 @@ describe("package kinds", () => {
1778
1896
  expect(isGameManifest(bot)).toBe(false);
1779
1897
  });
1780
1898
 
1781
- test("keep the old game schema name pointing at the game schema for this release", () => {
1899
+ it("keep the old game schema name pointing at the game schema for this release", () => {
1782
1900
  expect(manifestSchema).toBe(gameManifestSchema);
1783
1901
  });
1784
1902
 
1785
- test("read a bot package from its source", () => {
1903
+ it("read a bot package from its source", () => {
1786
1904
  const { manifest } = readPackage(yamlSource(botBase()));
1787
1905
 
1788
1906
  expect(manifest.kind).toBe(PackageKind.Bot);
1789
1907
  expect(manifest.metadata.id).toBe("bot-node");
1790
1908
  });
1791
1909
 
1792
- test("read a game package from its source unchanged", () => {
1910
+ it("read a game package from its source unchanged", () => {
1793
1911
  const { manifest } = readPackage(yamlSource(base()));
1794
1912
 
1795
1913
  expect(isGameManifest(manifest) && manifest.secrets).toEqual([]);
1796
1914
  });
1797
1915
 
1798
- test("render every kind literal into the JSON schema", () => {
1916
+ it("render every kind literal into the JSON schema", () => {
1799
1917
  const rendered = JSON.stringify(buildJsonSchema());
1800
1918
 
1801
1919
  for (const kind of Object.values(PackageKind)) {