@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,4 +1,4 @@
1
- import { describe, expect, test } from "bun:test";
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
- test("splits a key from its value", () => {
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
- test("accepts a key with no value", () => {
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
- test("ignores comments and blank lines", () => {
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
- test("unquotes a string and unescapes it", () => {
34
+ it("unquotes a string and unescapes it", () => {
35
35
  expect(decodeSourceCfgValue('"a \\"b\\" c"')).toBe('a "b" c');
36
36
  });
37
37
 
38
- test("reads booleans and numbers", () => {
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
- test("leaves a bare word alone", () => {
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
- test("quotes and escapes text", () => {
51
+ it("quotes and escapes text", () => {
52
52
  expect(encodeSourceCfgValue('a "b"')).toBe('"a \\"b\\""');
53
53
  });
54
54
 
55
- test("flattens newlines", () => {
55
+ it("flattens newlines", () => {
56
56
  expect(encodeSourceCfgValue("a\nb")).toBe('"a b"');
57
57
  });
58
58
 
59
- test("writes booleans and numbers bare", () => {
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
- test("reads a source cfg file", () => {
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
- test("rewrites a value and keeps the comments", () => {
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
- test("appends an unknown key", () => {
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
- test("collapses a repeated key it rewrites", () => {
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, test } from "bun:test";
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
- test("reads every property whichever way the attributes are ordered", () => {
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
- test("decodes entities", () => {
24
+ it("decodes entities", () => {
25
25
  expect(parseXmlProperties('<property name="ServerName" value="a &amp; b &lt;c&gt;"/>')).toEqual({
26
26
  ServerName: "a & b <c>",
27
27
  });
28
28
  });
29
29
 
30
- test("accepts single quoted attributes and an empty value", () => {
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
- test("ignores everything that is not a property", () => {
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
- test("rewrites a value in place and keeps order, comments and the header", () => {
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
- test("rewrites a property whose value attribute comes first", () => {
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
- test("adds an unknown property before the closing tag with the same indent", () => {
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
- test("escapes a value it writes", () => {
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 &amp; b &quot;c&quot;"/>');
95
95
  });
96
96
 
97
- test("adds a value attribute to a property that has none", () => {
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, test } from "bun:test";
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
- test("keeps a single socket protocol as it is", () => {
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
- test("expands a pair into both socket protocols", () => {
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
- test("never hands out the shared list itself", () => {
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
- test("reads a port stored before blocks existed as a single port", () => {
34
+ it("reads a port stored before blocks existed as a single port", () => {
35
35
  expect(portCountOf({})).toBe(1);
36
36
  });
37
37
 
38
- test("reads the count of a block", () => {
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, test } from "bun:test";
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
- test("reads as english in a log line and keeps both languages for the player", () => {
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
- test("keeps the cause a driver attaches for the staff log", () => {
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
- test("is the class a driver reaches through the published namespace", () => {
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, test } from "bun:test";
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
- test("recognises the shape a driver authors copy in", () => {
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
- test("leaves every other bridge value alone", () => {
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
- test("refuses a half-written text rather than rendering an empty language", () => {
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
- test('carries a localized cell beside its scalars so a driver never writes "English / عربي" into one string', () => {
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, test } from "bun:test";
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
- test("says the file is gone for 404 and 410", () => {
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
- test("says the source refused for 401 and 403", () => {
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
- "المصدر رفض تنزيل All the Mods 9.",
25
+ "رفض المصدر تنزيل All the Mods 9.",
26
26
  );
27
27
  });
28
28
 
29
- test("says the source is busy for 429", () => {
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
- test("says the source is down for a 5xx", () => {
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 bit.",
35
+ "The source of All the Mods 9 is down right now. Try again in a moment.",
36
36
  );
37
37
  });
38
38
 
39
- test("says the source could not be reached when there is no status", () => {
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 could not reach the source to download All the Mods 9. Try again.",
41
+ "We couldn't reach the source to download All the Mods 9. Try again.",
42
42
  );
43
43
  });
44
44
 
45
- test("falls back to a plain download failure for anything else", () => {
46
- const generic = "We could not download All the Mods 9. Try again.";
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
- test("always writes both languages", () => {
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: `ما قدرنا نوصل للمصدر عشان ننزّل ${subject}. جرّب مرة ثانية.`,
33
- en: `We could not reach the source to download ${subject}. Try again.`,
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: `المصدر رفض تنزيل ${subject}.`,
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: `المصدر مشغول الحين وما قدرنا ننزّل ${subject}. جرّب بعد شوي.`,
54
- en: `The source is busy right now, so we could not download ${subject}. Try again in a bit.`,
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: `مصدر ${subject} فيه عطل الحين. جرّب بعد شوي.`,
61
- en: `The source of ${subject} is down right now. Try again in a bit.`,
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: `ما قدرنا ننزّل ${subject}. جرّب مرة ثانية.`,
67
- en: `We could not download ${subject}. Try again.`,
66
+ ar: `تعذّر تنزيل ${subject}، جرب مرة ثانية.`,
67
+ en: `We couldn't download ${subject}. Try again.`,
68
68
  };
69
69
  };
@@ -1,11 +1,11 @@
1
- import { describe, expect, test } from "bun:test";
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
- test("parses the existing block vocabulary", () => {
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
- test("still rejects what it always rejected", () => {
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
- test("parses an inline variable", () => {
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
- test("accepts a panel variable", () => {
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
- test("rejects an unknown key", () => {
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
- test("rejects a lowercase panel key", () => {
83
+ it("rejects a lowercase panel key", () => {
84
84
  expect(() => parse("{{panel.motd}} is set.")).toThrow(/unknown variable/);
85
85
  });
86
86
 
87
- test("rejects an unterminated marker", () => {
87
+ it("rejects an unterminated marker", () => {
88
88
  expect(() => parse("Your address is {{server.address.")).toThrow(/unterminated \{\{ marker/);
89
89
  });
90
90
 
91
- test("rejects a variable inside a link target", () => {
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
- test("parses a field", () => {
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
- test("rejects a field with an unknown key", () => {
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
- test("parses a command and collects it", () => {
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
- test("rejects an empty command", () => {
124
+ it("rejects an empty command", () => {
125
125
  expect(() => parse("@[command]()")).toThrow(/a command must be a single line/);
126
126
  });
127
127
 
128
- test("parses every open target", () => {
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
- test("rejects a panel target without a tab id", () => {
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
- test("rejects an unknown open target", () => {
194
+ it("rejects an unknown open target", () => {
195
195
  expect(() => parse("@[open](nowhere)")).toThrow(/unknown open target "nowhere"/);
196
196
  });
197
197
 
198
- test("rejects an argument on a target that takes none", () => {
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
- test("rejects a traversing file path", () => {
202
+ it("rejects a traversing file path", () => {
203
203
  expect(() => parse("@[open](files:../../etc/passwd)")).toThrow();
204
204
  });
205
205
 
206
- test("rejects an unknown directive", () => {
206
+ it("rejects an unknown directive", () => {
207
207
  expect(() => parse("@[embed](something)")).toThrow(/unknown directive @\[embed\]/);
208
208
  });
209
209
 
210
- test("keeps the youtube directive working", () => {
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
- test("does not swallow a directive into the paragraph above it", () => {
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
- test("parses both branches", () => {
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
- test("allows a missing else branch", () => {
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
- test("keeps blocks around a conditional", () => {
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
- test("parses two conditionals in a row", () => {
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
- test("rejects nesting", () => {
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
- test("rejects an unterminated block", () => {
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
- test("rejects an empty when branch", () => {
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
- test("rejects a stray close", () => {
373
+ it("rejects a stray close", () => {
374
374
  expect(() => parse(":::")).toThrow(/unexpected ::: without a matching :::when/);
375
375
  });
376
376
 
377
- test("rejects an unknown key", () => {
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
- test("rejects a malformed opener", () => {
389
+ it("rejects a malformed opener", () => {
390
390
  expect(() => parse(":::if server.address")).toThrow(/conditionals open with :::when/);
391
391
  });
392
392
  });