@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.
- package/package.json +3 -2
- package/rules/package.md +65 -0
- package/src/blog/articleFrontmatter.test.ts +10 -10
- package/src/blog/compile.test.ts +9 -9
- package/src/bot/botDeploy.test.ts +16 -16
- package/src/bot/botEnvironment.test.ts +8 -8
- package/src/bot/botIdentity.test.ts +12 -12
- package/src/bot/botPanel.test.ts +13 -13
- package/src/bot/botPanel.ts +4 -4
- package/src/bot/botState.test.ts +3 -3
- package/src/bot/botTerminal.test.ts +5 -5
- package/src/bot/botVariables.test.ts +8 -8
- package/src/bot/detect/botToken.test.ts +6 -6
- package/src/bot/detect/detect.test.ts +21 -21
- package/src/bot/detect/detectTable.test.ts +5 -5
- package/src/bot/detect/envExample.test.ts +2 -2
- package/src/bot/detect/envScan.test.ts +4 -4
- package/src/bot/detect/inferBotPlatform.test.ts +4 -4
- package/src/bot/detect/keys.test.ts +3 -3
- package/src/bot/detect/sensitiveKey.test.ts +5 -5
- package/src/bot/detect/serverkJson.test.ts +5 -5
- package/src/catalogs/curseforge.test.ts +35 -35
- package/src/catalogs/thunderstore.test.ts +9 -9
- package/src/cli/detectTable.test.ts +3 -3
- package/src/cli/link.test.ts +11 -11
- package/src/cli/main.ts +8 -0
- package/src/cli/rules.test.ts +198 -0
- package/src/cli/rules.ts +188 -0
- package/src/cli/validate.test.ts +179 -31
- package/src/cli/validate.ts +44 -0
- package/src/codecs/ini.test.ts +13 -13
- package/src/codecs/javaProperties.test.ts +11 -11
- package/src/codecs/sourceCfg.test.ts +14 -14
- package/src/codecs/xmlProperties.test.ts +10 -10
- package/src/core/bridgePort.test.ts +6 -6
- package/src/core/bridgeUserError.test.ts +4 -4
- package/src/core/bridgeValue.test.ts +5 -5
- package/src/core/describeDownloadFailure.test.ts +12 -12
- package/src/core/describeDownloadFailure.ts +10 -10
- package/src/guides/markdown.test.ts +31 -31
- package/src/install/installStamp.test.ts +9 -9
- package/src/manifest/extension.ts +36 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +208 -90
- package/src/manifest/manifest.ts +34 -0
- package/src/net/loopback.test.ts +10 -10
- package/src/presence/rosterSync.test.ts +7 -7
- package/src/probes/a2s.test.ts +5 -5
- package/src/probes/minecraftPing.test.ts +17 -17
- package/src/probes/raknetPing.test.ts +13 -13
- package/src/protocol/bridgePreview.ts +1 -0
- package/src/rcon/rconAccess.test.ts +20 -20
- package/src/rcon/rconAccess.ts +14 -14
- package/src/rcon/rconClient.test.ts +49 -49
- package/src/rcon/rconFrame.test.ts +14 -14
- package/src/rcon/rconIds.test.ts +4 -4
- package/src/rcon/rconWriter.test.ts +6 -6
- package/src/runtime/bridgeActivity.test.ts +15 -15
- package/src/runtime/bridgeBackupLease.test.ts +9 -9
- package/src/runtime/bridgeConfigReader.test.ts +5 -5
- package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
- package/src/runtime/bridgeContext.test.ts +5 -5
- package/src/runtime/bridgeDriver.test.ts +94 -94
- package/src/runtime/bridgeEvents.test.ts +15 -15
- package/src/runtime/bridgeFiles.test.ts +54 -54
- package/src/runtime/bridgeInstall.test.ts +10 -10
- package/src/runtime/bridgeLiveConfig.test.ts +6 -6
- package/src/runtime/bridgePending.test.ts +6 -6
- package/src/runtime/bridgeRedaction.test.ts +16 -16
- package/src/runtime/bridgeRegistry.test.ts +84 -52
- package/src/runtime/bridgeRegistry.ts +2 -2
- package/src/runtime/bridgeServer.test.ts +31 -31
- package/src/runtime/bridgeSetup.test.ts +27 -27
- package/src/runtime/bridgeSetup.ts +1 -1
- package/src/runtime/bridgeSetupHandlers.test.ts +13 -13
- package/src/runtime/bridgeSignal.test.ts +7 -7
- package/src/runtime/bridgeSupervisor.test.ts +42 -42
- package/src/runtime/serve.ts +4 -4
- package/src/steam/avatars.test.ts +13 -13
- package/src/steam/steamApp.test.ts +8 -8
- package/src/steam/steamcmd.test.ts +17 -17
- package/src/steam/steamcmd.ts +4 -4
- package/src/steam/steamcmdNarrator.test.ts +4 -4
- package/src/steam/workshop.test.ts +5 -5
- package/src/utils/changedSettingKeys.test.ts +5 -5
- package/src/utils/describeExecFailure.test.ts +4 -4
- package/src/utils/describeExecFailure.ts +1 -1
- package/src/utils/sameSettingValue.test.ts +7 -7
- package/src/utils/token.test.ts +7 -7
- package/src/wine/protonCommand.test.ts +5 -5
- package/src/wine/wineCommand.test.ts +6 -6
- package/src/wine/winePrefix.test.ts +6 -6
- package/src/wire/activity.test.ts +8 -8
- package/src/wire/panel.test.ts +28 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
decodeSourceCfgValue,
|
|
4
4
|
encodeSourceCfgValue,
|
|
@@ -8,21 +8,21 @@ import {
|
|
|
8
8
|
} from "./sourceCfg";
|
|
9
9
|
|
|
10
10
|
describe("readSourceCfgEntry", () => {
|
|
11
|
-
|
|
11
|
+
it("splits a key from its value", () => {
|
|
12
12
|
expect(readSourceCfgEntry('hostname "serverk"')).toEqual({
|
|
13
13
|
key: "hostname",
|
|
14
14
|
value: '"serverk"',
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
it("accepts a key with no value", () => {
|
|
19
19
|
expect(readSourceCfgEntry("sv_lan")).toEqual({
|
|
20
20
|
key: "sv_lan",
|
|
21
21
|
value: "",
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
it("ignores comments and blank lines", () => {
|
|
26
26
|
expect(readSourceCfgEntry("// a comment")).toBeNull();
|
|
27
27
|
expect(readSourceCfgEntry("# a comment")).toBeNull();
|
|
28
28
|
expect(readSourceCfgEntry(" ")).toBeNull();
|
|
@@ -31,32 +31,32 @@ describe("readSourceCfgEntry", () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
describe("decodeSourceCfgValue", () => {
|
|
34
|
-
|
|
34
|
+
it("unquotes a string and unescapes it", () => {
|
|
35
35
|
expect(decodeSourceCfgValue('"a \\"b\\" c"')).toBe('a "b" c');
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
it("reads booleans and numbers", () => {
|
|
39
39
|
expect(decodeSourceCfgValue("true")).toBe(true);
|
|
40
40
|
expect(decodeSourceCfgValue("false")).toBe(false);
|
|
41
41
|
expect(decodeSourceCfgValue("42")).toBe(42);
|
|
42
42
|
expect(decodeSourceCfgValue("-1.5")).toBe(-1.5);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
it("leaves a bare word alone", () => {
|
|
46
46
|
expect(decodeSourceCfgValue("de_dust2")).toBe("de_dust2");
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
describe("encodeSourceCfgValue", () => {
|
|
51
|
-
|
|
51
|
+
it("quotes and escapes text", () => {
|
|
52
52
|
expect(encodeSourceCfgValue('a "b"')).toBe('"a \\"b\\""');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
it("flattens newlines", () => {
|
|
56
56
|
expect(encodeSourceCfgValue("a\nb")).toBe('"a b"');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
it("writes booleans and numbers bare", () => {
|
|
60
60
|
expect(encodeSourceCfgValue(true)).toBe("true");
|
|
61
61
|
expect(encodeSourceCfgValue(7)).toBe("7");
|
|
62
62
|
expect(encodeSourceCfgValue(null)).toBe('""');
|
|
@@ -64,7 +64,7 @@ describe("encodeSourceCfgValue", () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
describe("parseSourceCfg", () => {
|
|
67
|
-
|
|
67
|
+
it("reads a source cfg file", () => {
|
|
68
68
|
expect(parseSourceCfg('// header\nhostname "serverk"\nsv_maxplayers 24\nsv_cheats false\n')).toEqual({
|
|
69
69
|
hostname: "serverk",
|
|
70
70
|
sv_maxplayers: 24,
|
|
@@ -74,7 +74,7 @@ describe("parseSourceCfg", () => {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
describe("mergeSourceCfg", () => {
|
|
77
|
-
|
|
77
|
+
it("rewrites a value and keeps the comments", () => {
|
|
78
78
|
expect(
|
|
79
79
|
mergeSourceCfg('// header\nhostname "old"\nsv_maxplayers 24\n', {
|
|
80
80
|
hostname: "new",
|
|
@@ -82,7 +82,7 @@ describe("mergeSourceCfg", () => {
|
|
|
82
82
|
).toBe('// header\nhostname "new"\nsv_maxplayers 24\n');
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
it("appends an unknown key", () => {
|
|
86
86
|
expect(
|
|
87
87
|
mergeSourceCfg('hostname "x"\n', {
|
|
88
88
|
rcon_password: "abc",
|
|
@@ -90,7 +90,7 @@ describe("mergeSourceCfg", () => {
|
|
|
90
90
|
).toBe('hostname "x"\nrcon_password "abc"\n');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
it("collapses a repeated key it rewrites", () => {
|
|
94
94
|
expect(
|
|
95
95
|
mergeSourceCfg("map de_dust2\nmap de_nuke\n", {
|
|
96
96
|
map: "de_inferno",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { mergeXmlProperties, parseXmlProperties } from "./xmlProperties";
|
|
3
3
|
|
|
4
4
|
const SAMPLE = [
|
|
@@ -13,7 +13,7 @@ const SAMPLE = [
|
|
|
13
13
|
].join("\n");
|
|
14
14
|
|
|
15
15
|
describe("parseXmlProperties", () => {
|
|
16
|
-
|
|
16
|
+
it("reads every property whichever way the attributes are ordered", () => {
|
|
17
17
|
expect(parseXmlProperties(SAMPLE)).toEqual({
|
|
18
18
|
ServerName: "serverk",
|
|
19
19
|
ServerPort: "26900",
|
|
@@ -21,25 +21,25 @@ describe("parseXmlProperties", () => {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
it("decodes entities", () => {
|
|
25
25
|
expect(parseXmlProperties('<property name="ServerName" value="a & b <c>"/>')).toEqual({
|
|
26
26
|
ServerName: "a & b <c>",
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
it("accepts single quoted attributes and an empty value", () => {
|
|
31
31
|
expect(parseXmlProperties("<property name='ServerPassword' value=''/>")).toEqual({
|
|
32
32
|
ServerPassword: "",
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
it("ignores everything that is not a property", () => {
|
|
37
37
|
expect(parseXmlProperties('<ServerSettings>\n<other name="x" value="1"/>\n</ServerSettings>')).toEqual({});
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
describe("mergeXmlProperties", () => {
|
|
42
|
-
|
|
42
|
+
it("rewrites a value in place and keeps order, comments and the header", () => {
|
|
43
43
|
expect(
|
|
44
44
|
mergeXmlProperties(SAMPLE, {
|
|
45
45
|
ServerPort: "26950",
|
|
@@ -58,7 +58,7 @@ describe("mergeXmlProperties", () => {
|
|
|
58
58
|
);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
it("rewrites a property whose value attribute comes first", () => {
|
|
62
62
|
expect(
|
|
63
63
|
mergeXmlProperties(SAMPLE, {
|
|
64
64
|
ServerMaxPlayerCount: "16",
|
|
@@ -66,7 +66,7 @@ describe("mergeXmlProperties", () => {
|
|
|
66
66
|
).toContain('<property value="16" name="ServerMaxPlayerCount"/>');
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
it("adds an unknown property before the closing tag with the same indent", () => {
|
|
70
70
|
expect(
|
|
71
71
|
mergeXmlProperties(SAMPLE, {
|
|
72
72
|
ServerPassword: "1234",
|
|
@@ -86,7 +86,7 @@ describe("mergeXmlProperties", () => {
|
|
|
86
86
|
);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
it("escapes a value it writes", () => {
|
|
90
90
|
expect(
|
|
91
91
|
mergeXmlProperties(SAMPLE, {
|
|
92
92
|
ServerName: 'a & b "c"',
|
|
@@ -94,7 +94,7 @@ describe("mergeXmlProperties", () => {
|
|
|
94
94
|
).toContain('<property name="ServerName" value="a & b "c""/>');
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
it("adds a value attribute to a property that has none", () => {
|
|
98
98
|
expect(
|
|
99
99
|
mergeXmlProperties('<property name="ServerName"/>', {
|
|
100
100
|
ServerName: "x",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { PortProtocol, portCountOf, SOCKET_PROTOCOLS, socketProtocolsOf } from "./bridgePort";
|
|
3
3
|
|
|
4
4
|
describe("socketProtocolsOf", () => {
|
|
5
|
-
|
|
5
|
+
it("keeps a single socket protocol as it is", () => {
|
|
6
6
|
expect(socketProtocolsOf(PortProtocol.Tcp)).toEqual([
|
|
7
7
|
PortProtocol.Tcp,
|
|
8
8
|
]);
|
|
@@ -11,14 +11,14 @@ describe("socketProtocolsOf", () => {
|
|
|
11
11
|
]);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
it("expands a pair into both socket protocols", () => {
|
|
15
15
|
expect(socketProtocolsOf(PortProtocol.TcpUdp)).toEqual([
|
|
16
16
|
PortProtocol.Tcp,
|
|
17
17
|
PortProtocol.Udp,
|
|
18
18
|
]);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
it("never hands out the shared list itself", () => {
|
|
22
22
|
const expanded = socketProtocolsOf(PortProtocol.TcpUdp);
|
|
23
23
|
|
|
24
24
|
expanded.pop();
|
|
@@ -31,11 +31,11 @@ describe("socketProtocolsOf", () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
describe("portCountOf", () => {
|
|
34
|
-
|
|
34
|
+
it("reads a port stored before blocks existed as a single port", () => {
|
|
35
35
|
expect(portCountOf({})).toBe(1);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
it("reads the count of a block", () => {
|
|
39
39
|
expect(
|
|
40
40
|
portCountOf({
|
|
41
41
|
count: 32,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { Bridge } from "../modules";
|
|
3
3
|
import { BridgeUserError } from "./bridgeUserError";
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ describe("BridgeUserError", () => {
|
|
|
8
8
|
en: "this is not a file we are waiting for",
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
it("reads as english in a log line and keeps both languages for the player", () => {
|
|
12
12
|
const error = new BridgeUserError(text);
|
|
13
13
|
|
|
14
14
|
expect(error).toBeInstanceOf(Error);
|
|
@@ -17,7 +17,7 @@ describe("BridgeUserError", () => {
|
|
|
17
17
|
expect(error.text).toEqual(text);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
it("keeps the cause a driver attaches for the staff log", () => {
|
|
21
21
|
const cause = new Error("cdn.modrinth.com answered 404");
|
|
22
22
|
const error = new BridgeUserError(text, {
|
|
23
23
|
cause,
|
|
@@ -27,7 +27,7 @@ describe("BridgeUserError", () => {
|
|
|
27
27
|
expect(error.message).toBe(text.en);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
it("is the class a driver reaches through the published namespace", () => {
|
|
31
31
|
expect(new Bridge.UserError(text)).toBeInstanceOf(BridgeUserError);
|
|
32
32
|
});
|
|
33
33
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import type { BridgeRow } from "./bridgeValue";
|
|
3
3
|
import { isLocalizedText } from "./bridgeValue";
|
|
4
4
|
|
|
5
5
|
describe("isLocalizedText", () => {
|
|
6
|
-
|
|
6
|
+
it("recognises the shape a driver authors copy in", () => {
|
|
7
7
|
expect(
|
|
8
8
|
isLocalizedText({
|
|
9
9
|
ar: "التركيب",
|
|
@@ -12,7 +12,7 @@ describe("isLocalizedText", () => {
|
|
|
12
12
|
).toBe(true);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
it("leaves every other bridge value alone", () => {
|
|
16
16
|
expect(isLocalizedText("Hytale")).toBe(false);
|
|
17
17
|
expect(isLocalizedText(12)).toBe(false);
|
|
18
18
|
expect(isLocalizedText(true)).toBe(false);
|
|
@@ -20,7 +20,7 @@ describe("isLocalizedText", () => {
|
|
|
20
20
|
expect(isLocalizedText(undefined)).toBe(false);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
it("refuses a half-written text rather than rendering an empty language", () => {
|
|
24
24
|
expect(
|
|
25
25
|
isLocalizedText({
|
|
26
26
|
en: "Setup",
|
|
@@ -30,7 +30,7 @@ describe("isLocalizedText", () => {
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
describe("BridgeRow", () => {
|
|
33
|
-
|
|
33
|
+
it('carries a localized cell beside its scalars so a driver never writes "English / عربي" into one string', () => {
|
|
34
34
|
const row: BridgeRow = {
|
|
35
35
|
id: "u-1",
|
|
36
36
|
kills: 12,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { BridgeDownloadError } from "./bridgeDownloadError";
|
|
3
3
|
import { BridgeNetError } from "./bridgeNetError";
|
|
4
4
|
import { describeDownloadFailure } from "./describeDownloadFailure";
|
|
@@ -6,7 +6,7 @@ import { describeDownloadFailure } from "./describeDownloadFailure";
|
|
|
6
6
|
const SUBJECT = "All the Mods 9";
|
|
7
7
|
|
|
8
8
|
describe("describeDownloadFailure", () => {
|
|
9
|
-
|
|
9
|
+
it("says the file is gone for 404 and 410", () => {
|
|
10
10
|
for (const status of [
|
|
11
11
|
404,
|
|
12
12
|
410,
|
|
@@ -17,39 +17,39 @@ describe("describeDownloadFailure", () => {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
it("says the source refused for 401 and 403", () => {
|
|
21
21
|
expect(describeDownloadFailure(new BridgeNetError(403, "forbidden"), SUBJECT).en).toBe(
|
|
22
22
|
"The source refused the download of All the Mods 9.",
|
|
23
23
|
);
|
|
24
24
|
expect(describeDownloadFailure(new BridgeDownloadError(401, "unauthorized"), SUBJECT).ar).toBe(
|
|
25
|
-
"المصدر
|
|
25
|
+
"رفض المصدر تنزيل All the Mods 9.",
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
it("says the source is busy for 429", () => {
|
|
30
30
|
expect(describeDownloadFailure(new BridgeDownloadError(429, "slow down"), SUBJECT).en).toContain("busy");
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
it("says the source is down for a 5xx", () => {
|
|
34
34
|
expect(describeDownloadFailure(new BridgeNetError(503, "unavailable"), SUBJECT).en).toBe(
|
|
35
|
-
"The source of All the Mods 9 is down right now. Try again in a
|
|
35
|
+
"The source of All the Mods 9 is down right now. Try again in a moment.",
|
|
36
36
|
);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
it("says the source could not be reached when there is no status", () => {
|
|
40
40
|
expect(describeDownloadFailure(new BridgeDownloadError(null, "ECONNRESET"), SUBJECT).en).toBe(
|
|
41
|
-
"We
|
|
41
|
+
"We couldn't reach the source to download All the Mods 9. Try again.",
|
|
42
42
|
);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
const generic = "We
|
|
45
|
+
it("falls back to a plain download failure for anything else", () => {
|
|
46
|
+
const generic = "We couldn't download All the Mods 9. Try again.";
|
|
47
47
|
|
|
48
48
|
expect(describeDownloadFailure(new BridgeDownloadError(400, "bad request"), SUBJECT).en).toBe(generic);
|
|
49
49
|
expect(describeDownloadFailure(new Error("digest mismatch"), SUBJECT).en).toBe(generic);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
it("always writes both languages", () => {
|
|
53
53
|
const text = describeDownloadFailure(new BridgeDownloadError(500, "boom"), SUBJECT);
|
|
54
54
|
|
|
55
55
|
expect(text.ar.length).toBeGreaterThan(0);
|
|
@@ -29,41 +29,41 @@ export const describeDownloadFailure = (error: unknown, subject: string): Locali
|
|
|
29
29
|
|
|
30
30
|
if (status === null) {
|
|
31
31
|
return {
|
|
32
|
-
ar:
|
|
33
|
-
en: `We
|
|
32
|
+
ar: `تعذّر الوصول إلى المصدر لتنزيل ${subject}، جرب مرة ثانية.`,
|
|
33
|
+
en: `We couldn't reach the source to download ${subject}. Try again.`,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
if (status !== undefined && REMOVED_STATUSES.includes(status)) {
|
|
38
38
|
return {
|
|
39
|
-
ar: `${subject}
|
|
39
|
+
ar: `${subject} لم يعد متوفرًا لدى المصدر.`,
|
|
40
40
|
en: `${subject} is no longer available from its source.`,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
if (status !== undefined && REFUSED_STATUSES.includes(status)) {
|
|
45
45
|
return {
|
|
46
|
-
ar:
|
|
46
|
+
ar: `رفض المصدر تنزيل ${subject}.`,
|
|
47
47
|
en: `The source refused the download of ${subject}.`,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (status === BUSY_STATUS) {
|
|
52
52
|
return {
|
|
53
|
-
ar: `المصدر مشغول
|
|
54
|
-
en: `The source is busy right now, so we
|
|
53
|
+
ar: `المصدر مشغول حاليًا وتعذّر تنزيل ${subject}، عطه لحظة وجرب مرة ثانية.`,
|
|
54
|
+
en: `The source is busy right now, so we couldn't download ${subject}. Give it a moment and try again.`,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
if (status !== undefined && status >= SERVER_ERROR_STATUS) {
|
|
59
59
|
return {
|
|
60
|
-
ar:
|
|
61
|
-
en: `The source of ${subject} is down right now. Try again in a
|
|
60
|
+
ar: `يواجه مصدر ${subject} عطلًا حاليًا، جرب مرة ثانية بعد لحظة.`,
|
|
61
|
+
en: `The source of ${subject} is down right now. Try again in a moment.`,
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
return {
|
|
66
|
-
ar:
|
|
67
|
-
en: `We
|
|
66
|
+
ar: `تعذّر تنزيل ${subject}، جرب مرة ثانية.`,
|
|
67
|
+
en: `We couldn't download ${subject}. Try again.`,
|
|
68
68
|
};
|
|
69
69
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { GuideOpenTab } from "./blocks";
|
|
3
3
|
import { parseGuideMarkdown } from "./markdown";
|
|
4
4
|
|
|
5
5
|
const parse = (text: string) => parseGuideMarkdown("guides/test/en.md", text);
|
|
6
6
|
|
|
7
7
|
describe("static blocks", () => {
|
|
8
|
-
|
|
8
|
+
it("parses the existing block vocabulary", () => {
|
|
9
9
|
const { blocks } = parse(
|
|
10
10
|
[
|
|
11
11
|
"## Join the game",
|
|
@@ -32,7 +32,7 @@ describe("static blocks", () => {
|
|
|
32
32
|
]);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
it("still rejects what it always rejected", () => {
|
|
36
36
|
expect(() => parse("# Title")).toThrow(/h1 is not supported/);
|
|
37
37
|
expect(() => parse("| a | b |")).toThrow(/tables are not supported/);
|
|
38
38
|
expect(() => parse("<div>hi</div>")).toThrow(/raw HTML/);
|
|
@@ -41,7 +41,7 @@ describe("static blocks", () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
describe("variables", () => {
|
|
44
|
-
|
|
44
|
+
it("parses an inline variable", () => {
|
|
45
45
|
const { blocks } = parse("Your address is {{server.address}} — paste it in.");
|
|
46
46
|
|
|
47
47
|
expect(blocks[0]).toEqual({
|
|
@@ -63,7 +63,7 @@ describe("variables", () => {
|
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
it("accepts a panel variable", () => {
|
|
67
67
|
const { blocks } = parse("The motd is {{panel.MOTD}}.");
|
|
68
68
|
|
|
69
69
|
expect(blocks[0]).toMatchObject({
|
|
@@ -76,19 +76,19 @@ describe("variables", () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
it("rejects an unknown key", () => {
|
|
80
80
|
expect(() => parse("Your address is {{server.adress}}.")).toThrow(/unknown variable "server.adress"/);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
it("rejects a lowercase panel key", () => {
|
|
84
84
|
expect(() => parse("{{panel.motd}} is set.")).toThrow(/unknown variable/);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
it("rejects an unterminated marker", () => {
|
|
88
88
|
expect(() => parse("Your address is {{server.address.")).toThrow(/unterminated \{\{ marker/);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
it("rejects a variable inside a link target", () => {
|
|
92
92
|
expect(() => parse("See [your server](/server/{{server.code}}).")).toThrow(
|
|
93
93
|
/variables cannot be used inside a link target/,
|
|
94
94
|
);
|
|
@@ -96,7 +96,7 @@ describe("variables", () => {
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
describe("directives", () => {
|
|
99
|
-
|
|
99
|
+
it("parses a field", () => {
|
|
100
100
|
const { blocks } = parse("@[field](server.address)");
|
|
101
101
|
|
|
102
102
|
expect(blocks[0]).toEqual({
|
|
@@ -105,11 +105,11 @@ describe("directives", () => {
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
it("rejects a field with an unknown key", () => {
|
|
109
109
|
expect(() => parse("@[field](server.nope)")).toThrow(/unknown variable "server.nope"/);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
it("parses a command and collects it", () => {
|
|
113
113
|
const result = parse("@[command](whitelist on)");
|
|
114
114
|
|
|
115
115
|
expect(result.blocks[0]).toEqual({
|
|
@@ -121,11 +121,11 @@ describe("directives", () => {
|
|
|
121
121
|
]);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
it("rejects an empty command", () => {
|
|
125
125
|
expect(() => parse("@[command]()")).toThrow(/a command must be a single line/);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
it("parses every open target", () => {
|
|
129
129
|
const { blocks } = parse(
|
|
130
130
|
[
|
|
131
131
|
"@[open](console)",
|
|
@@ -187,27 +187,27 @@ describe("directives", () => {
|
|
|
187
187
|
]);
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
it("rejects a panel target without a tab id", () => {
|
|
191
191
|
expect(() => parse("@[open](panel)")).toThrow(/panel target needs a tab id/);
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
it("rejects an unknown open target", () => {
|
|
195
195
|
expect(() => parse("@[open](nowhere)")).toThrow(/unknown open target "nowhere"/);
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
it("rejects an argument on a target that takes none", () => {
|
|
199
199
|
expect(() => parse("@[open](setup:sign-in)")).toThrow(/"setup" target does not take an argument/);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
it("rejects a traversing file path", () => {
|
|
203
203
|
expect(() => parse("@[open](files:../../etc/passwd)")).toThrow();
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
it("rejects an unknown directive", () => {
|
|
207
207
|
expect(() => parse("@[embed](something)")).toThrow(/unknown directive @\[embed\]/);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
it("keeps the youtube directive working", () => {
|
|
211
211
|
const { blocks } = parse("@[youtube](dQw4w9WgXcQ)");
|
|
212
212
|
|
|
213
213
|
expect(blocks[0]).toEqual({
|
|
@@ -216,7 +216,7 @@ describe("directives", () => {
|
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
it("does not swallow a directive into the paragraph above it", () => {
|
|
220
220
|
const { blocks } = parse(
|
|
221
221
|
[
|
|
222
222
|
"Copy your address:",
|
|
@@ -232,7 +232,7 @@ describe("directives", () => {
|
|
|
232
232
|
});
|
|
233
233
|
|
|
234
234
|
describe("conditionals", () => {
|
|
235
|
-
|
|
235
|
+
it("parses both branches", () => {
|
|
236
236
|
const { blocks } = parse(
|
|
237
237
|
[
|
|
238
238
|
":::when server.address",
|
|
@@ -279,7 +279,7 @@ describe("conditionals", () => {
|
|
|
279
279
|
]);
|
|
280
280
|
});
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
it("allows a missing else branch", () => {
|
|
283
283
|
const { blocks } = parse(
|
|
284
284
|
[
|
|
285
285
|
":::when server.address",
|
|
@@ -294,7 +294,7 @@ describe("conditionals", () => {
|
|
|
294
294
|
});
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
it("keeps blocks around a conditional", () => {
|
|
298
298
|
const { blocks } = parse(
|
|
299
299
|
[
|
|
300
300
|
"## Join the game",
|
|
@@ -314,7 +314,7 @@ describe("conditionals", () => {
|
|
|
314
314
|
]);
|
|
315
315
|
});
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
it("parses two conditionals in a row", () => {
|
|
318
318
|
const { blocks } = parse(
|
|
319
319
|
[
|
|
320
320
|
":::when server.address",
|
|
@@ -332,7 +332,7 @@ describe("conditionals", () => {
|
|
|
332
332
|
]);
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
|
|
335
|
+
it("rejects nesting", () => {
|
|
336
336
|
expect(() =>
|
|
337
337
|
parse(
|
|
338
338
|
[
|
|
@@ -346,7 +346,7 @@ describe("conditionals", () => {
|
|
|
346
346
|
).toThrow(/conditionals cannot be nested/);
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
it("rejects an unterminated block", () => {
|
|
350
350
|
expect(() =>
|
|
351
351
|
parse(
|
|
352
352
|
[
|
|
@@ -357,7 +357,7 @@ describe("conditionals", () => {
|
|
|
357
357
|
).toThrow(/unterminated :::when block/);
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
-
|
|
360
|
+
it("rejects an empty when branch", () => {
|
|
361
361
|
expect(() =>
|
|
362
362
|
parse(
|
|
363
363
|
[
|
|
@@ -370,11 +370,11 @@ describe("conditionals", () => {
|
|
|
370
370
|
).toThrow(/empty :::when branch/);
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
|
|
373
|
+
it("rejects a stray close", () => {
|
|
374
374
|
expect(() => parse(":::")).toThrow(/unexpected ::: without a matching :::when/);
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
it("rejects an unknown key", () => {
|
|
378
378
|
expect(() =>
|
|
379
379
|
parse(
|
|
380
380
|
[
|
|
@@ -386,7 +386,7 @@ describe("conditionals", () => {
|
|
|
386
386
|
).toThrow(/unknown variable "server.nope"/);
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
-
|
|
389
|
+
it("rejects a malformed opener", () => {
|
|
390
390
|
expect(() => parse(":::if server.address")).toThrow(/conditionals open with :::when/);
|
|
391
391
|
});
|
|
392
392
|
});
|