@serverkgg/bridge 0.1.6 → 0.1.8
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 +1 -1
- package/rules/package.md +1 -0
- package/src/bot/botCauses.test.ts +48 -0
- package/src/bot/botCauses.ts +7 -1
- package/src/bot/detect/corpus/anidiotsguide-guidebot.json +2 -1
- package/src/bot/detect/corpus/latand-tgbot_template_v3.json +2 -1
- package/src/bot/detect/corpus/nezdemkovski-telegram-bot-boilerplate.json +2 -1
- package/src/bot/detect/detect.test.ts +39 -0
- package/src/bot/detect/envExample.test.ts +8 -0
- package/src/bot/detect/envExample.ts +3 -1
- package/src/cli/validate.test.ts +429 -3
- package/src/cli/validate.ts +225 -1
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +79 -0
- package/src/manifest/manifest.ts +5 -0
- package/src/manifest/steam.ts +14 -0
- package/src/steam/steamcmd.test.ts +367 -1
- package/src/steam/steamcmd.ts +154 -18
package/package.json
CHANGED
package/rules/package.md
CHANGED
|
@@ -51,6 +51,7 @@ One Serverk package (a game or a bot runtime) on `@serverkgg/bridge`, with no pi
|
|
|
51
51
|
- Write `ar:` before `en:` in every localized field.
|
|
52
52
|
- `secrets` are platform API keys the game can use, `required: false`; customer settings are a panel form, and a bot customer's own values go through `environment.vault`.
|
|
53
53
|
- Change an outcome by changing the declared value (`reset.keep: []`, `backup.only`), never by asking the platform to ignore a field.
|
|
54
|
+
- `files.protected` holds only `.serverk` paths: the stamp, sidecars, journals and driver scratch under `.serverk-staging/`. Never a drop point under `.serverk-uploads/`, a `files.notable` match, an `@[open](files:…)` target or a folder the customer collects a file from; `serverk-bridge validate` fails each but the last, a runtime path.
|
|
54
55
|
|
|
55
56
|
## Guides and image
|
|
56
57
|
|
|
@@ -13,6 +13,10 @@ const CASES: readonly {
|
|
|
13
13
|
line: "Error [TokenInvalid]: An invalid token was provided.",
|
|
14
14
|
cause: BridgeCrashCause.TokenInvalid,
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
line: "Error [TOKEN_INVALID]: An invalid token was provided.",
|
|
18
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
19
|
+
},
|
|
16
20
|
{
|
|
17
21
|
line: "discord.errors.LoginFailure: Improper token has been passed.",
|
|
18
22
|
cause: BridgeCrashCause.TokenInvalid,
|
|
@@ -45,6 +49,38 @@ const CASES: readonly {
|
|
|
45
49
|
line: "pyrogram.errors.exceptions.bad_request_400.AccessTokenExpired: Telegram says: [400 ACCESS_TOKEN_EXPIRED] - The bot token has expired",
|
|
46
50
|
cause: BridgeCrashCause.TokenInvalid,
|
|
47
51
|
},
|
|
52
|
+
{
|
|
53
|
+
line: "TypeError: expected token to be a str, received NoneType instead",
|
|
54
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
line: "TypeError: expected token to be a str, received <class 'NoneType'> instead",
|
|
58
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
line: "TypeError: token must be of type str, not NoneType",
|
|
62
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
line: "TypeError: token must be of type str, got NoneType instead",
|
|
66
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
line: "TypeError: The token provided was of type <class 'NoneType'> but was expected to be str",
|
|
70
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
line: "Error: Empty token!",
|
|
74
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
line: "aiogram.utils.token.TokenValidationError: Token is invalid!",
|
|
78
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
line: "aiogram.utils.token.TokenValidationError: Token is invalid! It must be 'str' type instead of <class 'NoneType'> type.",
|
|
82
|
+
cause: BridgeCrashCause.TokenInvalid,
|
|
83
|
+
},
|
|
48
84
|
{
|
|
49
85
|
line: "Error: Used disallowed intents",
|
|
50
86
|
cause: BridgeCrashCause.IntentsDisallowed,
|
|
@@ -53,6 +89,10 @@ const CASES: readonly {
|
|
|
53
89
|
line: "Error [DisallowedIntents]: Privileged intent provided is not enabled or whitelisted.",
|
|
54
90
|
cause: BridgeCrashCause.IntentsDisallowed,
|
|
55
91
|
},
|
|
92
|
+
{
|
|
93
|
+
line: "Error [DISALLOWED_INTENTS]: Privileged intent provided is not enabled or whitelisted.",
|
|
94
|
+
cause: BridgeCrashCause.IntentsDisallowed,
|
|
95
|
+
},
|
|
56
96
|
{
|
|
57
97
|
line: "discord.errors.PrivilegedIntentsRequired: Shard ID None is requesting privileged intents that have not been explicitly enabled in the developer portal.",
|
|
58
98
|
cause: BridgeCrashCause.IntentsDisallowed,
|
|
@@ -265,6 +305,14 @@ describe("BOT_CRASH_EVIDENCE", () => {
|
|
|
265
305
|
expect(ring.crash(recordOf())).toBe(BridgeCrashCause.Unknown);
|
|
266
306
|
});
|
|
267
307
|
|
|
308
|
+
it("leaves a TypeError about something other than the token unexplained", () => {
|
|
309
|
+
const { ring, print } = botRing();
|
|
310
|
+
|
|
311
|
+
print("TypeError: expected str, bytes or os.PathLike object, not NoneType");
|
|
312
|
+
|
|
313
|
+
expect(ring.crash(recordOf())).toBe(BridgeCrashCause.Unknown);
|
|
314
|
+
});
|
|
315
|
+
|
|
268
316
|
it("calls exit 137 out of memory only when the record carries the flag", () => {
|
|
269
317
|
const unflagged = botRing();
|
|
270
318
|
|
package/src/bot/botCauses.ts
CHANGED
|
@@ -13,7 +13,13 @@ export const BOT_CRASH_EVIDENCE: readonly BridgeEventPattern[] = [
|
|
|
13
13
|
emit: BridgeEventName.TokenInvalid,
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
match:
|
|
16
|
+
match:
|
|
17
|
+
/TypeError: (?:expected token to be a str, received|token must be of type str, (?:not|got) |The token provided was of type .+ but was expected to be str)|\bError: Empty token!|TokenValidationError: Token is invalid!/,
|
|
18
|
+
emit: BridgeEventName.TokenInvalid,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
match:
|
|
22
|
+
/Used disallowed intents|\[DisallowedIntents\]|Privileged intent provided is not enabled or whitelisted|PrivilegedIntentsRequired|close code:? 4014\b/,
|
|
17
23
|
emit: BridgeEventName.IntentsDisallowed,
|
|
18
24
|
},
|
|
19
25
|
{
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"yarn.lock"
|
|
19
19
|
],
|
|
20
20
|
"contents": {
|
|
21
|
+
".env-example": "DISCORD_TOKEN=\nOWNER=",
|
|
21
22
|
"package.json": "{\n \"name\": \"guidebot\",\n \"version\": \"2.1.0\",\n \"description\": \"A boilerplate example bot with command handler and reloadable commands. Updated and Maintained by the Idiot's Guide Community\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"node index.js\"\n },\n \"engines\": {\n \"node\": \">=16\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/AnIdiotsGuide/guidebot.git\"\n },\n \"author\": \"Evelyne Lachance <eslachance@gmail.com> (http://evie.codes)\",\n \"contributors\": [\n \"York (http://anidiots.guide)\"\n ],\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/AnIdiotsGuide/guidebot/issues\"\n },\n \"homepage\": \"https://github.com/AnIdiotsGuide/guidebot#readme\",\n \"dependencies\": {\n \"@discordjs/builders\": \"^0.2.0\",\n \"@sapphire/time-utilities\": \"^1.3.8\",\n \"colorette\": \"^1.3.0\",\n \"discord.js\": \"^13.0.1\",\n \"dotenv\": \"^10.0.0\",\n \"enmap\": \"^5.8.5\"\n },\n \"devDependencies\": {\n \"eslint\": \"^7.30.0\",\n \"eslint-config-airbnb-base\": \"^14.2.1\",\n \"eslint-config-standard\": \"^16.0.3\",\n \"eslint-plugin-import\": \"^2.23.4\",\n \"eslint-plugin-node\": \"^11.1.0\",\n \"eslint-plugin-promise\": \"^5.1.0\"\n }\n}\n"
|
|
22
23
|
},
|
|
23
24
|
"expected": {
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"start": "node index.js",
|
|
31
32
|
"nodeVersion": "16",
|
|
32
33
|
"pythonVersion": null,
|
|
33
|
-
"tokenKey":
|
|
34
|
+
"tokenKey": "DISCORD_TOKEN",
|
|
34
35
|
"warnings": []
|
|
35
36
|
}
|
|
36
37
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"requirements.txt"
|
|
18
18
|
],
|
|
19
19
|
"contents": {
|
|
20
|
+
".env.dist": "BOT_TOKEN=123456:Your-TokEn_ExaMple\nADMINS=123456,654321\nUSE_REDIS=False\n\n#POSTGRES_USER=someusername\n#POSTGRES_PASSWORD=postgres_pass12345\n#POSTGRES_DB=bot\n#DB_HOST=pg_database\n\n# REDIS_HOST=redis_cache\n# REDIS_PORT=6388\n# REDIS_DB=1\n# REDIS_PASSWORD=someredispass\n\n\n#WEBHOOK_EXPOSE=8001\n#WEBHOOK_APP_NAME=webhook\n",
|
|
20
21
|
"requirements.txt": "aiogram~=3.0\nenvirons~=9.0\nredis\nbetterlogging\n\n# # For enabling api:\n# backoff\n# ujson\n# yarl\n\n# # For PostgreSQL sqlalchemy + alembic:\n# sqlalchemy~=2.0\n# alembic~=1.0\n# asyncpg\n"
|
|
21
22
|
},
|
|
22
23
|
"expected": {
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"start": "python bot.py",
|
|
30
31
|
"nodeVersion": null,
|
|
31
32
|
"pythonVersion": null,
|
|
32
|
-
"tokenKey":
|
|
33
|
+
"tokenKey": "BOT_TOKEN",
|
|
33
34
|
"warnings": [
|
|
34
35
|
"unpinned-requirements"
|
|
35
36
|
]
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"tsconfig.json"
|
|
22
22
|
],
|
|
23
23
|
"contents": {
|
|
24
|
+
".env-sample": "BOT_TOKEN=\"\"\nUSERNAME=\"\"\nWEBHOOK_URL=\"\"\nPORT=\"3000\"\nNODE_ENV=\"development\"\n",
|
|
24
25
|
"package.json": "{\n \"name\": \"telegram-bot-boilerplate\",\n \"version\": \"1.0.0\",\n \"description\": \"Telegram Bot Boilerplate\",\n \"author\": \"Yuri Nezdemkovski <yuri@nezdemkovski.com> (https://nezdemkovski.com/)\",\n \"homepage\": \"https://github.com/nezdemkovski/telegram-bot-boilerplate\",\n \"license\": \"MIT\",\n \"engines\": {\n \"bun\": \">=1\"\n },\n \"dependencies\": {\n \"grammy\": \"^1.40.0\"\n },\n \"devDependencies\": {\n \"bun-types\": \"1.3.9\",\n \"eslint\": \"^10.0.1\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"prettier\": \"^3.8.1\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.56.0\"\n },\n \"scripts\": {\n \"dev\": \"bun --watch src/index.ts\",\n \"build\": \"bun build src/index.ts --outdir dist --target bun --minify\",\n \"start\": \"NODE_ENV=production bun dist/index.js\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src\",\n \"format\": \"prettier --write 'src/**/*.ts'\"\n }\n}\n"
|
|
25
26
|
},
|
|
26
27
|
"expected": {
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"start": "NODE_ENV=production bun dist/index.js",
|
|
34
35
|
"nodeVersion": null,
|
|
35
36
|
"pythonVersion": null,
|
|
36
|
-
"tokenKey":
|
|
37
|
+
"tokenKey": "BOT_TOKEN",
|
|
37
38
|
"warnings": []
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -813,6 +813,45 @@ describe("detect env suggestions", () => {
|
|
|
813
813
|
]);
|
|
814
814
|
});
|
|
815
815
|
|
|
816
|
+
const CASES: readonly {
|
|
817
|
+
file: string;
|
|
818
|
+
}[] = [
|
|
819
|
+
{
|
|
820
|
+
file: ".env-example",
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
file: ".env-sample",
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
file: ".env.dist",
|
|
827
|
+
},
|
|
828
|
+
];
|
|
829
|
+
|
|
830
|
+
for (const item of CASES) {
|
|
831
|
+
it(`reads ${item.file} the way it reads .env.example`, () => {
|
|
832
|
+
const result = project({
|
|
833
|
+
"package.json": "{}",
|
|
834
|
+
[item.file]: "DISCORD_TOKEN=\nOWNER=\n",
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
expect(result.env).toEqual([
|
|
838
|
+
{
|
|
839
|
+
key: "DISCORD_TOKEN",
|
|
840
|
+
sensitive: true,
|
|
841
|
+
source: item.file,
|
|
842
|
+
example: null,
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
key: "OWNER",
|
|
846
|
+
sensitive: false,
|
|
847
|
+
source: item.file,
|
|
848
|
+
example: null,
|
|
849
|
+
},
|
|
850
|
+
]);
|
|
851
|
+
expect(result.tokenKey).toBe("DISCORD_TOKEN");
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
816
855
|
it("suggests the keys of a skipped .env after the example files, never its values", () => {
|
|
817
856
|
const result = detect({
|
|
818
857
|
files: [
|
|
@@ -4,11 +4,13 @@ export const ENV_EXAMPLE_FILES: readonly string[] = [
|
|
|
4
4
|
".env.example",
|
|
5
5
|
".env.sample",
|
|
6
6
|
".env.template",
|
|
7
|
+
".env-example",
|
|
8
|
+
".env-sample",
|
|
9
|
+
".env.dist",
|
|
7
10
|
];
|
|
8
11
|
|
|
9
12
|
const ENV_TEMPLATE_FILES: ReadonlySet<string> = new Set([
|
|
10
13
|
...ENV_EXAMPLE_FILES,
|
|
11
|
-
".env.dist",
|
|
12
14
|
".env.defaults",
|
|
13
15
|
]);
|
|
14
16
|
|
package/src/cli/validate.test.ts
CHANGED
|
@@ -4,9 +4,9 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { renderDetectTable } from "../bot/detect";
|
|
6
6
|
import { BridgeKind, BridgeSetupStepKind } from "../core";
|
|
7
|
-
import { GuideOpenTab } from "../guides";
|
|
8
|
-
import { MANIFEST_SCHEMA_VERSION, PackageKind } from "../manifest";
|
|
9
|
-
import type { BridgeDriver, BridgeSetupModule } from "../modules";
|
|
7
|
+
import { GuideOpenTab, type GuidePack, type ParsedBlock, type ParsedLeafBlock } from "../guides";
|
|
8
|
+
import { MANIFEST_SCHEMA_VERSION, PackageKind, type RunnableManifest } from "../manifest";
|
|
9
|
+
import type { BridgeDriver, BridgePendingModule, BridgeSetupModule } from "../modules";
|
|
10
10
|
import {
|
|
11
11
|
type BridgeCatalogSection,
|
|
12
12
|
BridgeConfirm,
|
|
@@ -23,9 +23,13 @@ import { RULES_FILE, readShippedRules, writeRules } from "./rules";
|
|
|
23
23
|
import {
|
|
24
24
|
availabilityReportLines,
|
|
25
25
|
confirmReportLines,
|
|
26
|
+
notableReportLines,
|
|
27
|
+
openTargetReportLines,
|
|
26
28
|
placementReportLines,
|
|
29
|
+
protectedNameReportLines,
|
|
27
30
|
runValidate,
|
|
28
31
|
setupReportLines,
|
|
32
|
+
uploadStagingReportLines,
|
|
29
33
|
} from "./validate";
|
|
30
34
|
|
|
31
35
|
const text = (value: string) => ({
|
|
@@ -822,3 +826,425 @@ describe("the confirm report", () => {
|
|
|
822
826
|
).toEqual([]);
|
|
823
827
|
});
|
|
824
828
|
});
|
|
829
|
+
|
|
830
|
+
const manifestWith = (rules: string[], notes: string[] = []) => {
|
|
831
|
+
return {
|
|
832
|
+
files: {
|
|
833
|
+
protected: rules,
|
|
834
|
+
notable: notes.map((match) => ({
|
|
835
|
+
match,
|
|
836
|
+
})),
|
|
837
|
+
},
|
|
838
|
+
} as unknown as RunnableManifest;
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
const worldsWith = (staging: string): BridgeTableSection => ({
|
|
842
|
+
layout: BridgeLayout.Table,
|
|
843
|
+
id: "worlds",
|
|
844
|
+
module: "worlds",
|
|
845
|
+
columns: [
|
|
846
|
+
{
|
|
847
|
+
key: "name",
|
|
848
|
+
label: text("Name"),
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
upload: {
|
|
852
|
+
label: text("Upload a world"),
|
|
853
|
+
extensions: [
|
|
854
|
+
"zip",
|
|
855
|
+
],
|
|
856
|
+
staging,
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
const pendingWith = (staging: string): BridgePendingModule => ({
|
|
861
|
+
kind: BridgeKind.Pending,
|
|
862
|
+
staging,
|
|
863
|
+
extensions: [
|
|
864
|
+
"jar",
|
|
865
|
+
],
|
|
866
|
+
list: async () => [],
|
|
867
|
+
add: async () => {
|
|
868
|
+
return;
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
const driverWith = (
|
|
873
|
+
sections: BridgeTab["sections"],
|
|
874
|
+
pending?: BridgePendingModule,
|
|
875
|
+
setup?: BridgeSetupModule,
|
|
876
|
+
): BridgeDriver => ({
|
|
877
|
+
install: {
|
|
878
|
+
kind: BridgeKind.Install,
|
|
879
|
+
run: async () => {
|
|
880
|
+
return;
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
lifecycle: {
|
|
884
|
+
kind: BridgeKind.Lifecycle,
|
|
885
|
+
command: async () => [
|
|
886
|
+
"run",
|
|
887
|
+
],
|
|
888
|
+
},
|
|
889
|
+
pending,
|
|
890
|
+
setup,
|
|
891
|
+
panel: {
|
|
892
|
+
tabs: [
|
|
893
|
+
{
|
|
894
|
+
id: "worlds",
|
|
895
|
+
title: text("Worlds"),
|
|
896
|
+
sections,
|
|
897
|
+
},
|
|
898
|
+
],
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
const packWith = (blocks: ParsedBlock[]): GuidePack => ({
|
|
903
|
+
drafts: [
|
|
904
|
+
{
|
|
905
|
+
id: "getting-started",
|
|
906
|
+
title: text("Getting started"),
|
|
907
|
+
summary: text("Start here"),
|
|
908
|
+
tags: [],
|
|
909
|
+
surfaces: [],
|
|
910
|
+
videoId: null,
|
|
911
|
+
body: {
|
|
912
|
+
ar: blocks,
|
|
913
|
+
en: blocks,
|
|
914
|
+
},
|
|
915
|
+
},
|
|
916
|
+
],
|
|
917
|
+
files: [],
|
|
918
|
+
images: [],
|
|
919
|
+
commands: [],
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
const filesOpenWith = (path: string | null): ParsedLeafBlock => ({
|
|
923
|
+
type: "open",
|
|
924
|
+
target: {
|
|
925
|
+
tab: GuideOpenTab.Files,
|
|
926
|
+
path,
|
|
927
|
+
},
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
const SERVERK_RULES = [
|
|
931
|
+
".serverk-install.json",
|
|
932
|
+
".serverk/**",
|
|
933
|
+
"plugins/.serverk-catalog.json",
|
|
934
|
+
".serverk-staging/**",
|
|
935
|
+
".serverk-wipe",
|
|
936
|
+
"Server/mods/.serverk-mods.json",
|
|
937
|
+
];
|
|
938
|
+
|
|
939
|
+
const FOREIGN_RULES = [
|
|
940
|
+
"bedrock_server",
|
|
941
|
+
"*.so",
|
|
942
|
+
"worlds/*/db/**",
|
|
943
|
+
"config",
|
|
944
|
+
".serverkfoo",
|
|
945
|
+
];
|
|
946
|
+
|
|
947
|
+
const JAVA_RULES = [
|
|
948
|
+
".serverk-install.json",
|
|
949
|
+
".serverk-crashscan.json",
|
|
950
|
+
".serverk-companions.json",
|
|
951
|
+
".serverk-modpack.json",
|
|
952
|
+
"plugins/.serverk-catalog.json",
|
|
953
|
+
"mods/.serverk-catalog.json",
|
|
954
|
+
];
|
|
955
|
+
|
|
956
|
+
describe("protectedNameReportLines", () => {
|
|
957
|
+
it("passes rules that each name a .serverk path", () => {
|
|
958
|
+
expect(protectedNameReportLines(manifestWith(SERVERK_RULES))).toEqual([]);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
for (const rule of FOREIGN_RULES) {
|
|
962
|
+
it(`fails "${rule}", which names no .serverk path`, () => {
|
|
963
|
+
expect(
|
|
964
|
+
protectedNameReportLines(
|
|
965
|
+
manifestWith([
|
|
966
|
+
".serverk-install.json",
|
|
967
|
+
rule,
|
|
968
|
+
]),
|
|
969
|
+
),
|
|
970
|
+
).toEqual([
|
|
971
|
+
` error: files.protected "${rule}" is not a .serverk path — a package protects only platform state, so drop the rule or move the file under a .serverk name`,
|
|
972
|
+
]);
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
describe("uploadStagingReportLines", () => {
|
|
978
|
+
it("fails a section upload that stages under a protected folder", () => {
|
|
979
|
+
const manifest = manifestWith([
|
|
980
|
+
".serverk-download/**",
|
|
981
|
+
".serverk-worlds/**",
|
|
982
|
+
]);
|
|
983
|
+
|
|
984
|
+
expect(
|
|
985
|
+
uploadStagingReportLines(
|
|
986
|
+
manifest,
|
|
987
|
+
driverWith([
|
|
988
|
+
worldsWith(".serverk-worlds"),
|
|
989
|
+
]),
|
|
990
|
+
),
|
|
991
|
+
).toEqual([
|
|
992
|
+
' error: upload staging ".serverk-worlds" of tab "worlds" section "worlds" is protected by ".serverk-worlds/**" — a protected path refuses the customer\'s upload, so take the folder off `files.protected`',
|
|
993
|
+
]);
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it("fails a pending upload that stages under a protected folder", () => {
|
|
997
|
+
const manifest = manifestWith([
|
|
998
|
+
".serverk-staging",
|
|
999
|
+
]);
|
|
1000
|
+
|
|
1001
|
+
expect(uploadStagingReportLines(manifest, driverWith([], pendingWith(".serverk-staging/pending-upload")))).toEqual([
|
|
1002
|
+
' error: upload staging ".serverk-staging/pending-upload" of the pending upload is protected by ".serverk-staging" — a protected path refuses the customer\'s upload, so take the folder off `files.protected`',
|
|
1003
|
+
]);
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it("passes when no protected rule reaches an upload staging folder", () => {
|
|
1007
|
+
const manifest = manifestWith([
|
|
1008
|
+
".serverk-download/**",
|
|
1009
|
+
".serverk-install.json",
|
|
1010
|
+
".serverk-staging/install/**",
|
|
1011
|
+
"*.json",
|
|
1012
|
+
]);
|
|
1013
|
+
|
|
1014
|
+
expect(
|
|
1015
|
+
uploadStagingReportLines(
|
|
1016
|
+
manifest,
|
|
1017
|
+
driverWith(
|
|
1018
|
+
[
|
|
1019
|
+
worldsWith(".serverk-staging/world-upload"),
|
|
1020
|
+
],
|
|
1021
|
+
pendingWith(".serverk-staging/pending-upload"),
|
|
1022
|
+
),
|
|
1023
|
+
),
|
|
1024
|
+
).toEqual([]);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it("passes an upload staging folder that sits beside a protected sibling", () => {
|
|
1028
|
+
const manifest = manifestWith([
|
|
1029
|
+
".serverk-staging/**",
|
|
1030
|
+
]);
|
|
1031
|
+
|
|
1032
|
+
expect(
|
|
1033
|
+
uploadStagingReportLines(
|
|
1034
|
+
manifest,
|
|
1035
|
+
driverWith([
|
|
1036
|
+
worldsWith(".serverk-uploads/world"),
|
|
1037
|
+
]),
|
|
1038
|
+
),
|
|
1039
|
+
).toEqual([]);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it("passes a staging folder that merely holds a protected sidecar", () => {
|
|
1043
|
+
const manifest = manifestWith([
|
|
1044
|
+
".serverk-staging/install/**",
|
|
1045
|
+
]);
|
|
1046
|
+
|
|
1047
|
+
expect(
|
|
1048
|
+
uploadStagingReportLines(
|
|
1049
|
+
manifest,
|
|
1050
|
+
driverWith([
|
|
1051
|
+
worldsWith(".serverk-staging"),
|
|
1052
|
+
]),
|
|
1053
|
+
),
|
|
1054
|
+
).toEqual([]);
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
describe("notableReportLines", () => {
|
|
1059
|
+
it("fails a note on a protected file", () => {
|
|
1060
|
+
const manifest = manifestWith(
|
|
1061
|
+
[
|
|
1062
|
+
".serverk-install.json",
|
|
1063
|
+
],
|
|
1064
|
+
[
|
|
1065
|
+
".serverk-install.json",
|
|
1066
|
+
],
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
expect(notableReportLines(manifest)).toEqual([
|
|
1070
|
+
' error: files.notable ".serverk-install.json" is protected by ".serverk-install.json" — the file manager hides a protected path before it annotates it, so the note never shows; take one of the two off',
|
|
1071
|
+
]);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it("fails a note on a file inside a protected folder", () => {
|
|
1075
|
+
const manifest = manifestWith(
|
|
1076
|
+
[
|
|
1077
|
+
".serverk/**",
|
|
1078
|
+
],
|
|
1079
|
+
[
|
|
1080
|
+
".serverk/install.json",
|
|
1081
|
+
],
|
|
1082
|
+
);
|
|
1083
|
+
|
|
1084
|
+
expect(notableReportLines(manifest)).toEqual([
|
|
1085
|
+
' error: files.notable ".serverk/install.json" is protected by ".serverk/**" — the file manager hides a protected path before it annotates it, so the note never shows; take one of the two off',
|
|
1086
|
+
]);
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it("fails a wildcard note under a protected folder", () => {
|
|
1090
|
+
const manifest = manifestWith(
|
|
1091
|
+
[
|
|
1092
|
+
".serverk-staging",
|
|
1093
|
+
],
|
|
1094
|
+
[
|
|
1095
|
+
".serverk-staging/**",
|
|
1096
|
+
],
|
|
1097
|
+
);
|
|
1098
|
+
|
|
1099
|
+
expect(notableReportLines(manifest)).toEqual([
|
|
1100
|
+
' error: files.notable ".serverk-staging/**" is protected by ".serverk-staging" — the file manager hides a protected path before it annotates it, so the note never shows; take one of the two off',
|
|
1101
|
+
]);
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
it("passes notes on folders that merely hold protected sidecars", () => {
|
|
1105
|
+
const manifest = manifestWith(JAVA_RULES, [
|
|
1106
|
+
"plugins",
|
|
1107
|
+
"mods",
|
|
1108
|
+
"world/**",
|
|
1109
|
+
"*.json",
|
|
1110
|
+
]);
|
|
1111
|
+
|
|
1112
|
+
expect(notableReportLines(manifest)).toEqual([]);
|
|
1113
|
+
});
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
describe("openTargetReportLines", () => {
|
|
1117
|
+
it("fails a guide link that opens a protected file", () => {
|
|
1118
|
+
const manifest = manifestWith([
|
|
1119
|
+
".serverk-install.json",
|
|
1120
|
+
]);
|
|
1121
|
+
|
|
1122
|
+
expect(
|
|
1123
|
+
openTargetReportLines(
|
|
1124
|
+
manifest,
|
|
1125
|
+
driverWith([]),
|
|
1126
|
+
packWith([
|
|
1127
|
+
filesOpenWith(".serverk-install.json"),
|
|
1128
|
+
]),
|
|
1129
|
+
),
|
|
1130
|
+
).toEqual([
|
|
1131
|
+
' error: guide "getting-started" (ar) opens files at ".serverk-install.json", which ".serverk-install.json" protects — the panel refuses a protected path, so the link is dead; point it elsewhere or take the rule off `files.protected`',
|
|
1132
|
+
' error: guide "getting-started" (en) opens files at ".serverk-install.json", which ".serverk-install.json" protects — the panel refuses a protected path, so the link is dead; point it elsewhere or take the rule off `files.protected`',
|
|
1133
|
+
]);
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it("fails a guide link inside a conditional block", () => {
|
|
1137
|
+
const manifest = manifestWith([
|
|
1138
|
+
".serverk/**",
|
|
1139
|
+
]);
|
|
1140
|
+
|
|
1141
|
+
expect(
|
|
1142
|
+
openTargetReportLines(
|
|
1143
|
+
manifest,
|
|
1144
|
+
driverWith([]),
|
|
1145
|
+
packWith([
|
|
1146
|
+
{
|
|
1147
|
+
type: "conditional",
|
|
1148
|
+
key: "panel.modded",
|
|
1149
|
+
when: [],
|
|
1150
|
+
otherwise: [
|
|
1151
|
+
filesOpenWith(".serverk/install.json"),
|
|
1152
|
+
],
|
|
1153
|
+
},
|
|
1154
|
+
]),
|
|
1155
|
+
),
|
|
1156
|
+
).toEqual([
|
|
1157
|
+
' error: guide "getting-started" (ar) opens files at ".serverk/install.json", which ".serverk/**" protects — the panel refuses a protected path, so the link is dead; point it elsewhere or take the rule off `files.protected`',
|
|
1158
|
+
' error: guide "getting-started" (en) opens files at ".serverk/install.json", which ".serverk/**" protects — the panel refuses a protected path, so the link is dead; point it elsewhere or take the rule off `files.protected`',
|
|
1159
|
+
]);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
it("fails a setup step that opens a protected folder", () => {
|
|
1163
|
+
const manifest = manifestWith([
|
|
1164
|
+
".serverk-level/**",
|
|
1165
|
+
]);
|
|
1166
|
+
|
|
1167
|
+
const setup: BridgeSetupModule = {
|
|
1168
|
+
kind: BridgeKind.Setup,
|
|
1169
|
+
steps: [
|
|
1170
|
+
{
|
|
1171
|
+
kind: BridgeSetupStepKind.Open,
|
|
1172
|
+
id: "level",
|
|
1173
|
+
title: text("Level"),
|
|
1174
|
+
required: false,
|
|
1175
|
+
target: {
|
|
1176
|
+
tab: GuideOpenTab.Files,
|
|
1177
|
+
path: ".serverk-level",
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
],
|
|
1181
|
+
submit: async () => {
|
|
1182
|
+
return;
|
|
1183
|
+
},
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
expect(openTargetReportLines(manifest, driverWith([], undefined, setup), null)).toEqual([
|
|
1187
|
+
' error: setup step "level" opens files at ".serverk-level", which ".serverk-level/**" protects — the panel refuses a protected path, so the link is dead; point it elsewhere or take the rule off `files.protected`',
|
|
1188
|
+
]);
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
it("passes files links with no path, other tabs and unprotected paths", () => {
|
|
1192
|
+
const manifest = manifestWith([
|
|
1193
|
+
".serverk-install.json",
|
|
1194
|
+
".serverk-staging/**",
|
|
1195
|
+
]);
|
|
1196
|
+
|
|
1197
|
+
const setup: BridgeSetupModule = {
|
|
1198
|
+
kind: BridgeKind.Setup,
|
|
1199
|
+
steps: [
|
|
1200
|
+
{
|
|
1201
|
+
kind: BridgeSetupStepKind.Open,
|
|
1202
|
+
id: "files",
|
|
1203
|
+
title: text("Files"),
|
|
1204
|
+
required: false,
|
|
1205
|
+
target: {
|
|
1206
|
+
tab: GuideOpenTab.Files,
|
|
1207
|
+
path: null,
|
|
1208
|
+
},
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
kind: BridgeSetupStepKind.Open,
|
|
1212
|
+
id: "invite",
|
|
1213
|
+
title: text("Invite"),
|
|
1214
|
+
required: false,
|
|
1215
|
+
target: {
|
|
1216
|
+
tab: GuideOpenTab.Access,
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
],
|
|
1220
|
+
submit: async () => {
|
|
1221
|
+
return;
|
|
1222
|
+
},
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
expect(
|
|
1226
|
+
openTargetReportLines(
|
|
1227
|
+
manifest,
|
|
1228
|
+
driverWith([], undefined, setup),
|
|
1229
|
+
packWith([
|
|
1230
|
+
filesOpenWith(null),
|
|
1231
|
+
filesOpenWith("server.properties"),
|
|
1232
|
+
filesOpenWith(".serverk-uploads/world"),
|
|
1233
|
+
{
|
|
1234
|
+
type: "open",
|
|
1235
|
+
target: {
|
|
1236
|
+
tab: GuideOpenTab.Panel,
|
|
1237
|
+
tabId: "settings",
|
|
1238
|
+
},
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
type: "open",
|
|
1242
|
+
target: {
|
|
1243
|
+
tab: GuideOpenTab.Console,
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
]),
|
|
1247
|
+
),
|
|
1248
|
+
).toEqual([]);
|
|
1249
|
+
});
|
|
1250
|
+
});
|