@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.
- package/package.json +4 -3
- package/src/cli/validate.ts +1 -0
- package/src/context/bridgeContext.ts +10 -0
- package/src/context/index.ts +1 -0
- package/src/core/bridgeDownloadError.ts +10 -0
- package/src/core/bridgeHold.ts +12 -0
- package/src/core/bridgeKind.ts +2 -0
- package/src/core/bridgeUserError.test.ts +23 -0
- package/src/core/bridgeUserError.ts +12 -0
- package/src/core/index.ts +3 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +85 -0
- package/src/manifest/manifest.ts +21 -0
- package/src/manifest/presence.ts +48 -0
- package/src/manifest/primitives.ts +6 -0
- package/src/modules/bridge.ts +45 -2
- package/src/modules/bridgeDriver.ts +2 -0
- package/src/modules/bridgeModule.ts +25 -3
- package/src/panel/bridgeDetail.ts +49 -0
- package/src/panel/bridgeLayout.ts +1 -0
- package/src/panel/bridgePanel.ts +30 -0
- package/src/panel/bridgeUploadMode.ts +4 -0
- package/src/panel/index.ts +2 -0
- package/src/protocol/bridgeEgress.ts +8 -1
- package/src/protocol/bridgeEvents.ts +4 -0
- package/src/protocol/bridgeFrame.ts +28 -1
- package/src/protocol/bridgeLimits.ts +10 -0
- package/src/runtime/bridgeActions.ts +7 -0
- package/src/runtime/bridgeDriver.test.ts +477 -2
- package/src/runtime/bridgeDriver.ts +116 -3
- package/src/runtime/bridgeEgress.ts +3 -0
- package/src/runtime/bridgeEvents.ts +7 -5
- package/src/runtime/bridgeFiles.test.ts +625 -15
- package/src/runtime/bridgeFiles.ts +152 -45
- package/src/runtime/bridgeInstall.test.ts +118 -0
- package/src/runtime/bridgeInstall.ts +55 -0
- package/src/runtime/bridgeNet.ts +32 -3
- package/src/runtime/bridgePending.test.ts +148 -0
- package/src/runtime/bridgePending.ts +43 -0
- package/src/runtime/bridgePrivilege.test.ts +77 -0
- package/src/runtime/bridgePrivilege.ts +5 -3
- package/src/runtime/bridgeRegistry.test.ts +134 -0
- package/src/runtime/bridgeRegistry.ts +26 -4
- package/src/runtime/bridgeServer.test.ts +338 -0
- package/src/runtime/bridgeServer.ts +25 -3
- package/src/runtime/bridgeSupervisor.ts +18 -0
- package/src/runtime/bridgeZip.ts +179 -58
- package/src/runtime/serve.ts +64 -4
- package/src/wire/index.ts +2 -0
- package/src/wire/panel.test.ts +451 -0
- package/src/wire/panel.ts +295 -0
- package/src/wire/zEnvKey.ts +7 -0
package/src/panel/bridgePanel.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { BridgeControl } from "./bridgeControl";
|
|
|
5
5
|
import type { BridgeFormTarget } from "./bridgeFormTarget";
|
|
6
6
|
import type { BridgeIcon } from "./bridgeIcon";
|
|
7
7
|
import type { BridgeLayout } from "./bridgeLayout";
|
|
8
|
+
import type { BridgeUploadMode } from "./bridgeUploadMode";
|
|
8
9
|
|
|
9
10
|
export interface BridgeVisibleWhen {
|
|
10
11
|
variable: string;
|
|
@@ -44,6 +45,16 @@ export interface BridgeSectionAction {
|
|
|
44
45
|
fields?: BridgeField[];
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
export interface BridgeTabRef {
|
|
49
|
+
tab: string;
|
|
50
|
+
label: LocalizedText;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BridgeFormClear {
|
|
54
|
+
variable: string;
|
|
55
|
+
warning: LocalizedText;
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
export interface BridgeSectionAdd {
|
|
48
59
|
label: LocalizedText;
|
|
49
60
|
placeholder?: string;
|
|
@@ -53,6 +64,7 @@ export interface BridgeSectionUpload {
|
|
|
53
64
|
label: LocalizedText;
|
|
54
65
|
extensions: string[];
|
|
55
66
|
staging: string;
|
|
67
|
+
mode?: BridgeUploadMode;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
export interface BridgeActionsSection {
|
|
@@ -78,6 +90,7 @@ export interface BridgeFormSection {
|
|
|
78
90
|
confirm?: BridgeConfirm;
|
|
79
91
|
confirmText?: LocalizedText;
|
|
80
92
|
fields: BridgeField[];
|
|
93
|
+
clears?: BridgeFormClear[];
|
|
81
94
|
visibleWhen?: BridgeVisibleWhen;
|
|
82
95
|
access?: BridgeAccess;
|
|
83
96
|
}
|
|
@@ -130,10 +143,27 @@ export interface BridgeCatalogSection {
|
|
|
130
143
|
access?: BridgeAccess;
|
|
131
144
|
}
|
|
132
145
|
|
|
146
|
+
export interface BridgeDetailSection {
|
|
147
|
+
layout: BridgeLayout.Detail;
|
|
148
|
+
id: string;
|
|
149
|
+
title?: LocalizedText;
|
|
150
|
+
module: string;
|
|
151
|
+
actions?: BridgeSectionAction[];
|
|
152
|
+
reinstall?: boolean;
|
|
153
|
+
variable?: string;
|
|
154
|
+
confirm?: BridgeConfirm;
|
|
155
|
+
confirmText?: LocalizedText;
|
|
156
|
+
related?: BridgeTabRef;
|
|
157
|
+
empty?: LocalizedText;
|
|
158
|
+
visibleWhen?: BridgeVisibleWhen;
|
|
159
|
+
access?: BridgeAccess;
|
|
160
|
+
}
|
|
161
|
+
|
|
133
162
|
export type BridgeSection =
|
|
134
163
|
| BridgeActionsSection
|
|
135
164
|
| BridgeCardsSection
|
|
136
165
|
| BridgeCatalogSection
|
|
166
|
+
| BridgeDetailSection
|
|
137
167
|
| BridgeFormSection
|
|
138
168
|
| BridgeTableSection;
|
|
139
169
|
|
package/src/panel/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from "./bridgeAccess";
|
|
2
2
|
export * from "./bridgeConfirm";
|
|
3
3
|
export * from "./bridgeControl";
|
|
4
|
+
export * from "./bridgeDetail";
|
|
4
5
|
export * from "./bridgeFormTarget";
|
|
5
6
|
export * from "./bridgeIcon";
|
|
6
7
|
export * from "./bridgeLayout";
|
|
7
8
|
export * from "./bridgePanel";
|
|
9
|
+
export * from "./bridgeUploadMode";
|
|
@@ -4,18 +4,25 @@ export const BRIDGE_EGRESS_FRAME_LIMIT = 16 * 1024 * 1024;
|
|
|
4
4
|
|
|
5
5
|
export const BRIDGE_EGRESS_BODY_LIMIT = 8 * 1024 * 1024;
|
|
6
6
|
|
|
7
|
-
export const BRIDGE_EGRESS_DOWNLOAD_LIMIT =
|
|
7
|
+
export const BRIDGE_EGRESS_DOWNLOAD_LIMIT = 2 * 1024 * 1024 * 1024;
|
|
8
8
|
|
|
9
9
|
export enum BridgeEgressMethod {
|
|
10
10
|
Fetch = "fetch",
|
|
11
11
|
Download = "download",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export enum BridgeHttpMethod {
|
|
15
|
+
Get = "GET",
|
|
16
|
+
Post = "POST",
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export interface BridgeEgressRequest {
|
|
15
20
|
id: number;
|
|
16
21
|
token: string;
|
|
17
22
|
method: BridgeEgressMethod;
|
|
18
23
|
url: string;
|
|
24
|
+
httpMethod?: BridgeHttpMethod;
|
|
25
|
+
body?: string;
|
|
19
26
|
headers?: Record<string, string>;
|
|
20
27
|
timeoutMs?: number;
|
|
21
28
|
cache?: boolean;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export enum BridgeEventName {
|
|
2
2
|
GameModeChanged = "GameModeChanged",
|
|
3
|
+
MemoryUndersized = "MemoryUndersized",
|
|
3
4
|
MissingDependency = "MissingDependency",
|
|
5
|
+
ModCrashed = "ModCrashed",
|
|
6
|
+
PlayerAdvanced = "PlayerAdvanced",
|
|
7
|
+
PlayerDied = "PlayerDied",
|
|
4
8
|
PlayerJoined = "PlayerJoined",
|
|
5
9
|
PlayerLeft = "PlayerLeft",
|
|
6
10
|
PortBindFailed = "PortBindFailed",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BridgeDesired, BridgeRuntime, BridgeValues } from "../core";
|
|
1
|
+
import type { BridgeDesired, BridgeInstallHold, BridgeRuntime, BridgeValues, LocalizedText } from "../core";
|
|
2
2
|
|
|
3
3
|
export const BRIDGE_SOCKET_NAME = "bridge.sock";
|
|
4
4
|
|
|
@@ -19,6 +19,8 @@ export enum BridgeMethod {
|
|
|
19
19
|
Events = "events",
|
|
20
20
|
Invoke = "invoke",
|
|
21
21
|
Manifest = "manifest",
|
|
22
|
+
PendingAdd = "pendingAdd",
|
|
23
|
+
PendingList = "pendingList",
|
|
22
24
|
Quiesce = "quiesce",
|
|
23
25
|
Release = "release",
|
|
24
26
|
Sample = "sample",
|
|
@@ -59,6 +61,7 @@ export interface BridgeFailure {
|
|
|
59
61
|
error: string;
|
|
60
62
|
secret?: string;
|
|
61
63
|
failure?: string;
|
|
64
|
+
text?: LocalizedText;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export type BridgeResponse = BridgeFailure | BridgeSuccess;
|
|
@@ -68,8 +71,10 @@ export interface BridgeStatus {
|
|
|
68
71
|
runtime: BridgeRuntime;
|
|
69
72
|
desired: BridgeDesired;
|
|
70
73
|
exitCode: number | null;
|
|
74
|
+
outOfMemory: boolean;
|
|
71
75
|
restarts: number;
|
|
72
76
|
installError: string | null;
|
|
77
|
+
installHold: BridgeInstallHold | null;
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
export interface BridgeSampleResult {
|
|
@@ -95,6 +100,28 @@ export interface BridgeDescribeResult {
|
|
|
95
100
|
build: string | null;
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
export interface BridgePendingFile {
|
|
104
|
+
id: string;
|
|
105
|
+
title: LocalizedText;
|
|
106
|
+
fileName: string;
|
|
107
|
+
sizeBytes: number | null;
|
|
108
|
+
digest: string | null;
|
|
109
|
+
sourceUrl: string | null;
|
|
110
|
+
note: LocalizedText | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface BridgePendingRejection {
|
|
114
|
+
expected: string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface BridgePendingResult {
|
|
118
|
+
supported: boolean;
|
|
119
|
+
staging: string;
|
|
120
|
+
extensions: string[];
|
|
121
|
+
files: BridgePendingFile[];
|
|
122
|
+
rejected: BridgePendingRejection | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
export interface BridgeEvent {
|
|
99
126
|
seq: number;
|
|
100
127
|
event: string;
|
|
@@ -18,6 +18,14 @@ export const BRIDGE_VISIBLE_VALUES_LIMIT = 16;
|
|
|
18
18
|
|
|
19
19
|
export const BRIDGE_PREVIEW_LINES_LIMIT = 8;
|
|
20
20
|
|
|
21
|
+
export const BRIDGE_DETAIL_STATS_LIMIT = 12;
|
|
22
|
+
|
|
23
|
+
export const BRIDGE_DETAIL_BADGES_LIMIT = 4;
|
|
24
|
+
|
|
25
|
+
export const BRIDGE_DETAIL_LINKS_LIMIT = 4;
|
|
26
|
+
|
|
27
|
+
export const BRIDGE_FORM_CLEARS_LIMIT = 8;
|
|
28
|
+
|
|
21
29
|
export const BRIDGE_UPLOAD_EXTENSIONS_LIMIT = 8;
|
|
22
30
|
|
|
23
31
|
export const BRIDGE_MODULES_LIMIT = 32;
|
|
@@ -28,6 +36,8 @@ export const BRIDGE_DEPENDS_ON_LIMIT = 8;
|
|
|
28
36
|
|
|
29
37
|
export const BRIDGE_EVENTS_LIMIT = 32;
|
|
30
38
|
|
|
39
|
+
export const BRIDGE_PENDING_FILES_LIMIT = 64;
|
|
40
|
+
|
|
31
41
|
export const BRIDGE_ID_LENGTH_LIMIT = 32;
|
|
32
42
|
|
|
33
43
|
export const BRIDGE_KEY_LENGTH_LIMIT = 64;
|
|
@@ -42,6 +42,13 @@ export const actionsOf = (module: BridgePanelModule): string[] => {
|
|
|
42
42
|
];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
case BridgeKind.Detail: {
|
|
46
|
+
return [
|
|
47
|
+
BridgeAction.Read,
|
|
48
|
+
...Object.keys(module.actions ?? {}),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
case BridgeKind.Catalog: {
|
|
46
53
|
return [
|
|
47
54
|
BridgeAction.Install,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { BridgeKind, PortJoin, PortProtocol } from "../core";
|
|
3
3
|
import type { ManifestPort, ManifestPortActivation } from "../manifest";
|
|
4
|
-
import type { BridgeDriver } from "../modules";
|
|
4
|
+
import type { BridgeDriver, BridgePendingModule } from "../modules";
|
|
5
5
|
import {
|
|
6
6
|
BridgeConfirm,
|
|
7
7
|
BridgeControl,
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
BridgeFormTarget,
|
|
10
10
|
BridgeLayout,
|
|
11
11
|
type BridgeSectionAction,
|
|
12
|
+
type BridgeSectionUpload,
|
|
13
|
+
BridgeUploadMode,
|
|
12
14
|
type BridgeVisibleWhen,
|
|
13
15
|
} from "../panel";
|
|
14
16
|
import { validateDriver, validatePortActivation } from "./bridgeDriver";
|
|
@@ -647,6 +649,479 @@ describe("validatePortActivation", () => {
|
|
|
647
649
|
],
|
|
648
650
|
driverWithVariables(),
|
|
649
651
|
),
|
|
650
|
-
).toThrow('port "bedrock" activeWhen[0] names "CROSSPLAY", which no variables form or
|
|
652
|
+
).toThrow('port "bedrock" activeWhen[0] names "CROSSPLAY", which no variables form, catalog or detail declares');
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const driverWithDetail = (): BridgeDriver => ({
|
|
657
|
+
install: {
|
|
658
|
+
kind: BridgeKind.Install,
|
|
659
|
+
run: async () => {
|
|
660
|
+
return;
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
lifecycle: {
|
|
664
|
+
kind: BridgeKind.Lifecycle,
|
|
665
|
+
command: async () => [
|
|
666
|
+
"run",
|
|
667
|
+
],
|
|
668
|
+
},
|
|
669
|
+
modules: {
|
|
670
|
+
modpackStatus: {
|
|
671
|
+
kind: BridgeKind.Detail,
|
|
672
|
+
read: async () => null,
|
|
673
|
+
actions: {
|
|
674
|
+
update: async () => null,
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
panel: {
|
|
679
|
+
tabs: [
|
|
680
|
+
{
|
|
681
|
+
id: "version",
|
|
682
|
+
title: text("Version"),
|
|
683
|
+
sections: [
|
|
684
|
+
{
|
|
685
|
+
layout: BridgeLayout.Form,
|
|
686
|
+
id: "version",
|
|
687
|
+
target: BridgeFormTarget.Variables,
|
|
688
|
+
fields: [
|
|
689
|
+
field("MC_VERSION"),
|
|
690
|
+
],
|
|
691
|
+
clears: [
|
|
692
|
+
{
|
|
693
|
+
variable: "MODPACK",
|
|
694
|
+
warning: text("Applying this removes the modpack"),
|
|
695
|
+
},
|
|
696
|
+
],
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
layout: BridgeLayout.Detail,
|
|
700
|
+
id: "modpack-status",
|
|
701
|
+
module: "modpackStatus",
|
|
702
|
+
variable: "MODPACK",
|
|
703
|
+
reinstall: true,
|
|
704
|
+
related: {
|
|
705
|
+
tab: "modpacks",
|
|
706
|
+
label: text("Browse modpacks"),
|
|
707
|
+
},
|
|
708
|
+
actions: [
|
|
709
|
+
{
|
|
710
|
+
id: "update",
|
|
711
|
+
label: text("Update"),
|
|
712
|
+
},
|
|
713
|
+
],
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
id: "modpacks",
|
|
719
|
+
title: text("Modpacks"),
|
|
720
|
+
sections: [
|
|
721
|
+
{
|
|
722
|
+
layout: BridgeLayout.Actions,
|
|
723
|
+
id: "noop",
|
|
724
|
+
module: "noop",
|
|
725
|
+
actions: [
|
|
726
|
+
{
|
|
727
|
+
id: "ping",
|
|
728
|
+
label: text("Ping"),
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
},
|
|
732
|
+
],
|
|
733
|
+
},
|
|
734
|
+
],
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const withNoop = (driver: BridgeDriver): BridgeDriver => ({
|
|
739
|
+
...driver,
|
|
740
|
+
modules: {
|
|
741
|
+
...driver.modules,
|
|
742
|
+
noop: {
|
|
743
|
+
kind: BridgeKind.Actions,
|
|
744
|
+
actions: {
|
|
745
|
+
ping: async () => {
|
|
746
|
+
return;
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const detailSectionOf = (driver: BridgeDriver) => {
|
|
754
|
+
const section = driver.panel?.tabs[0]?.sections[1];
|
|
755
|
+
|
|
756
|
+
if (section?.layout !== BridgeLayout.Detail) {
|
|
757
|
+
throw new Error("fixture is missing its detail section");
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return section;
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const clearingFormOf = (driver: BridgeDriver) => {
|
|
764
|
+
const section = driver.panel?.tabs[0]?.sections[0];
|
|
765
|
+
|
|
766
|
+
if (section?.layout !== BridgeLayout.Form) {
|
|
767
|
+
throw new Error("fixture is missing its form section");
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return section;
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
describe("detail sections", () => {
|
|
774
|
+
test("accepts a detail section on a detail module", () => {
|
|
775
|
+
expect(() => validateDriver(withNoop(driverWithDetail()))).not.toThrow();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test("rejects a section pointing at a module that is not a detail module", () => {
|
|
779
|
+
const driver = withNoop(driverWithDetail());
|
|
780
|
+
|
|
781
|
+
driver.modules = {
|
|
782
|
+
...driver.modules,
|
|
783
|
+
modpackStatus: {
|
|
784
|
+
kind: BridgeKind.Collection,
|
|
785
|
+
list: async () => [],
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
expect(() => validateDriver(driver)).toThrow('references "modpackStatus", which is not a detail module');
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test("rejects an action the module does not implement", () => {
|
|
793
|
+
const driver = withNoop(driverWithDetail());
|
|
794
|
+
|
|
795
|
+
detailSectionOf(driver).actions = [
|
|
796
|
+
{
|
|
797
|
+
id: "reinstall",
|
|
798
|
+
label: text("Reinstall"),
|
|
799
|
+
},
|
|
800
|
+
];
|
|
801
|
+
|
|
802
|
+
expect(() => validateDriver(driver)).toThrow(
|
|
803
|
+
'declares action "reinstall", which module "modpackStatus" does not implement',
|
|
804
|
+
);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test("rejects a download action, since a detail has no rows to serve", () => {
|
|
808
|
+
const driver = withNoop(driverWithDetail());
|
|
809
|
+
|
|
810
|
+
detailSectionOf(driver).actions = [
|
|
811
|
+
{
|
|
812
|
+
id: "update",
|
|
813
|
+
label: text("Update"),
|
|
814
|
+
download: true,
|
|
815
|
+
},
|
|
816
|
+
];
|
|
817
|
+
|
|
818
|
+
expect(() => validateDriver(driver)).toThrow('declares action "update" as a download');
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("rejects a related tab the panel does not declare", () => {
|
|
822
|
+
const driver = withNoop(driverWithDetail());
|
|
823
|
+
|
|
824
|
+
detailSectionOf(driver).related = {
|
|
825
|
+
tab: "worlds",
|
|
826
|
+
label: text("Worlds"),
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
expect(() => validateDriver(driver)).toThrow('relates to tab "worlds", which the panel does not declare');
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test("counts a detail variable as declared for port activation", () => {
|
|
833
|
+
expect(() =>
|
|
834
|
+
validatePortActivation(
|
|
835
|
+
[
|
|
836
|
+
port([
|
|
837
|
+
{
|
|
838
|
+
variable: "MODPACK",
|
|
839
|
+
empty: false,
|
|
840
|
+
},
|
|
841
|
+
]),
|
|
842
|
+
],
|
|
843
|
+
driverWithDetail(),
|
|
844
|
+
),
|
|
845
|
+
).not.toThrow();
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
describe("form clears", () => {
|
|
850
|
+
test("accepts a clear of a variable a detail section declares", () => {
|
|
851
|
+
expect(() => validateDriver(withNoop(driverWithDetail()))).not.toThrow();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test("rejects a clear of a variable nothing declares", () => {
|
|
855
|
+
const driver = withNoop(driverWithDetail());
|
|
856
|
+
|
|
857
|
+
clearingFormOf(driver).clears = [
|
|
858
|
+
{
|
|
859
|
+
variable: "CROSSPLAY",
|
|
860
|
+
warning: text("Applying this turns crossplay off"),
|
|
861
|
+
},
|
|
862
|
+
];
|
|
863
|
+
|
|
864
|
+
expect(() => validateDriver(driver)).toThrow(
|
|
865
|
+
'clears "CROSSPLAY", which no variables form, catalog or detail declares',
|
|
866
|
+
);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test("rejects a clear of one of the form's own fields", () => {
|
|
870
|
+
const driver = withNoop(driverWithDetail());
|
|
871
|
+
|
|
872
|
+
clearingFormOf(driver).clears = [
|
|
873
|
+
{
|
|
874
|
+
variable: "MC_VERSION",
|
|
875
|
+
warning: text("Applying this resets the version"),
|
|
876
|
+
},
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
expect(() => validateDriver(driver)).toThrow('clears "MC_VERSION", which is one of its own fields');
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
test("rejects clears on a form that writes settings", () => {
|
|
883
|
+
const driver = withNoop(driverWithDetail());
|
|
884
|
+
const form = clearingFormOf(driver);
|
|
885
|
+
|
|
886
|
+
form.target = BridgeFormTarget.Settings;
|
|
887
|
+
form.module = "config";
|
|
888
|
+
|
|
889
|
+
driver.modules = {
|
|
890
|
+
...driver.modules,
|
|
891
|
+
config: {
|
|
892
|
+
kind: BridgeKind.Settings,
|
|
893
|
+
read: async () => ({}),
|
|
894
|
+
write: async () => {
|
|
895
|
+
return;
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
expect(() => validateDriver(driver)).toThrow("clears variables but does not target variables");
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
describe("table uploads", () => {
|
|
905
|
+
const driverWithUpload = (upload: BridgeSectionUpload): BridgeDriver => ({
|
|
906
|
+
install: {
|
|
907
|
+
kind: BridgeKind.Install,
|
|
908
|
+
run: async () => {
|
|
909
|
+
return;
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
lifecycle: {
|
|
913
|
+
kind: BridgeKind.Lifecycle,
|
|
914
|
+
command: async () => [
|
|
915
|
+
"run",
|
|
916
|
+
],
|
|
917
|
+
},
|
|
918
|
+
modules: {
|
|
919
|
+
modpackPending: {
|
|
920
|
+
kind: BridgeKind.Collection,
|
|
921
|
+
list: async () => [],
|
|
922
|
+
add: async () => {
|
|
923
|
+
return;
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
panel: {
|
|
928
|
+
tabs: [
|
|
929
|
+
{
|
|
930
|
+
id: "modpacks",
|
|
931
|
+
title: text("Modpacks"),
|
|
932
|
+
sections: [
|
|
933
|
+
{
|
|
934
|
+
layout: BridgeLayout.Table,
|
|
935
|
+
id: "pending",
|
|
936
|
+
module: "modpackPending",
|
|
937
|
+
columns: [
|
|
938
|
+
{
|
|
939
|
+
key: "name",
|
|
940
|
+
label: text("Name"),
|
|
941
|
+
},
|
|
942
|
+
],
|
|
943
|
+
upload,
|
|
944
|
+
},
|
|
945
|
+
],
|
|
946
|
+
},
|
|
947
|
+
],
|
|
948
|
+
},
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
const upload = (mode?: BridgeUploadMode): BridgeSectionUpload => ({
|
|
952
|
+
label: text("Upload"),
|
|
953
|
+
extensions: [
|
|
954
|
+
"jar",
|
|
955
|
+
],
|
|
956
|
+
staging: ".serverk-staging/pending-upload",
|
|
957
|
+
mode,
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
test("accepts an upload that declares no mode", () => {
|
|
961
|
+
expect(() => validateDriver(driverWithUpload(upload()))).not.toThrow();
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
test("accepts both declared upload modes", () => {
|
|
965
|
+
expect(() => validateDriver(driverWithUpload(upload(BridgeUploadMode.Archive)))).not.toThrow();
|
|
966
|
+
expect(() => validateDriver(driverWithUpload(upload(BridgeUploadMode.File)))).not.toThrow();
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
test("rejects an upload mode the platform does not know", () => {
|
|
970
|
+
const section = upload("stream" as BridgeUploadMode);
|
|
971
|
+
|
|
972
|
+
expect(() => validateDriver(driverWithUpload(section))).toThrow('declares an unknown upload mode "stream"');
|
|
973
|
+
});
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
describe("the pending module", () => {
|
|
977
|
+
const driverWithPending = (pending: BridgePendingModule): BridgeDriver => ({
|
|
978
|
+
install: {
|
|
979
|
+
kind: BridgeKind.Install,
|
|
980
|
+
run: async () => {
|
|
981
|
+
return;
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
lifecycle: {
|
|
985
|
+
kind: BridgeKind.Lifecycle,
|
|
986
|
+
command: async () => [
|
|
987
|
+
"run",
|
|
988
|
+
],
|
|
989
|
+
},
|
|
990
|
+
pending,
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
const pendingWith = (overrides: Partial<BridgePendingModule>): BridgePendingModule => ({
|
|
994
|
+
kind: BridgeKind.Pending,
|
|
995
|
+
staging: ".serverk-staging/pending-upload",
|
|
996
|
+
extensions: [
|
|
997
|
+
"jar",
|
|
998
|
+
],
|
|
999
|
+
list: async () => [],
|
|
1000
|
+
add: async () => {
|
|
1001
|
+
return;
|
|
1002
|
+
},
|
|
1003
|
+
...overrides,
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
test("accepts a driver that declares no pending module", () => {
|
|
1007
|
+
expect(() =>
|
|
1008
|
+
validateDriver({
|
|
1009
|
+
install: {
|
|
1010
|
+
kind: BridgeKind.Install,
|
|
1011
|
+
run: async () => {
|
|
1012
|
+
return;
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
lifecycle: {
|
|
1016
|
+
kind: BridgeKind.Lifecycle,
|
|
1017
|
+
command: async () => [
|
|
1018
|
+
"run",
|
|
1019
|
+
],
|
|
1020
|
+
},
|
|
1021
|
+
}),
|
|
1022
|
+
).not.toThrow();
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
test("accepts a pending module declaring a staging path and one extension", () => {
|
|
1026
|
+
expect(() => validateDriver(driverWithPending(pendingWith({})))).not.toThrow();
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
test("rejects a pending module carrying another kind", () => {
|
|
1030
|
+
const pending = pendingWith({
|
|
1031
|
+
kind: BridgeKind.Collection as BridgeKind.Pending,
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
expect(() => validateDriver(driverWithPending(pending))).toThrow(
|
|
1035
|
+
'the pending module declares kind "collection", which is not "pending"',
|
|
1036
|
+
);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
test("rejects a pending module with no staging path", () => {
|
|
1040
|
+
expect(() =>
|
|
1041
|
+
validateDriver(
|
|
1042
|
+
driverWithPending(
|
|
1043
|
+
pendingWith({
|
|
1044
|
+
staging: "",
|
|
1045
|
+
}),
|
|
1046
|
+
),
|
|
1047
|
+
),
|
|
1048
|
+
).toThrow("the pending module declares no staging path");
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
test("rejects a staging path longer than the platform takes", () => {
|
|
1052
|
+
expect(() =>
|
|
1053
|
+
validateDriver(
|
|
1054
|
+
driverWithPending(
|
|
1055
|
+
pendingWith({
|
|
1056
|
+
staging: "x".repeat(129),
|
|
1057
|
+
}),
|
|
1058
|
+
),
|
|
1059
|
+
),
|
|
1060
|
+
).toThrow("the pending module staging path is 129 characters — the platform takes at most 128");
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test("rejects a pending module declaring no extensions", () => {
|
|
1064
|
+
expect(() =>
|
|
1065
|
+
validateDriver(
|
|
1066
|
+
driverWithPending(
|
|
1067
|
+
pendingWith({
|
|
1068
|
+
extensions: [],
|
|
1069
|
+
}),
|
|
1070
|
+
),
|
|
1071
|
+
),
|
|
1072
|
+
).toThrow("the pending module declares no extensions");
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
test("rejects more extensions than the platform takes", () => {
|
|
1076
|
+
expect(() =>
|
|
1077
|
+
validateDriver(
|
|
1078
|
+
driverWithPending(
|
|
1079
|
+
pendingWith({
|
|
1080
|
+
extensions: Array.from({
|
|
1081
|
+
length: 9,
|
|
1082
|
+
}).map((_entry, index) => `e${index}`),
|
|
1083
|
+
}),
|
|
1084
|
+
),
|
|
1085
|
+
),
|
|
1086
|
+
).toThrow("the pending module declares 9 extensions — the platform takes at most 8");
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
test("rejects an extension longer than the platform takes", () => {
|
|
1090
|
+
expect(() =>
|
|
1091
|
+
validateDriver(
|
|
1092
|
+
driverWithPending(
|
|
1093
|
+
pendingWith({
|
|
1094
|
+
extensions: [
|
|
1095
|
+
"x".repeat(17),
|
|
1096
|
+
],
|
|
1097
|
+
}),
|
|
1098
|
+
),
|
|
1099
|
+
),
|
|
1100
|
+
).toThrow('the pending module extension "xxxxxxxxxxxxxxxxx" is 17 characters — the platform takes at most 16');
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
test("reserves the pending module id for the singleton", () => {
|
|
1104
|
+
expect(() =>
|
|
1105
|
+
validateDriver({
|
|
1106
|
+
install: {
|
|
1107
|
+
kind: BridgeKind.Install,
|
|
1108
|
+
run: async () => {
|
|
1109
|
+
return;
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
1112
|
+
lifecycle: {
|
|
1113
|
+
kind: BridgeKind.Lifecycle,
|
|
1114
|
+
command: async () => [
|
|
1115
|
+
"run",
|
|
1116
|
+
],
|
|
1117
|
+
},
|
|
1118
|
+
modules: {
|
|
1119
|
+
pending: {
|
|
1120
|
+
kind: BridgeKind.Collection,
|
|
1121
|
+
list: async () => [],
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
}),
|
|
1125
|
+
).toThrow('module id "pending" is reserved');
|
|
651
1126
|
});
|
|
652
1127
|
});
|