@serverkgg/bridge 0.1.9 → 0.1.11
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/src/bot/botCauses.test.ts +137 -0
- package/src/bot/botCauses.ts +13 -4
- package/src/bot/botEnvironment.test.ts +218 -1
- package/src/bot/botEnvironment.ts +85 -3
- package/src/bot/botVariables.test.ts +32 -0
- package/src/bot/botVariables.ts +9 -0
- package/src/bot/detect/botInspectWarning.ts +1 -0
- package/src/bot/detect/corpus/anonymousx1025-anonxmusic.json +3 -2
- package/src/bot/detect/corpus/bot-base-telegram-bot-template.json +3 -1
- package/src/bot/detect/corpus/nezdemkovski-telegram-bot-boilerplate.json +3 -1
- package/src/bot/detect/corpus/serverk-discord-node.json +3 -2
- package/src/bot/detect/corpus/serverk-discord-python.json +3 -2
- package/src/bot/detect/corpus/serverk-telegram-node.json +3 -2
- package/src/bot/detect/corpus/serverk-telegram-python.json +3 -2
- package/src/bot/detect/detect.test.ts +307 -0
- package/src/bot/detect/detect.ts +39 -14
- package/src/bot/detect/detectTable.ts +4 -0
- package/src/bot/detect/detectedVersion.test.ts +160 -0
- package/src/bot/detect/detectedVersion.ts +55 -0
- package/src/bot/detect/envExample.test.ts +4 -0
- package/src/bot/detect/envExample.ts +1 -0
- package/src/bot/detect/envScan.test.ts +110 -0
- package/src/bot/detect/envScan.ts +7 -3
- package/src/bot/detect/keys.test.ts +50 -0
- package/src/bot/detect/keys.ts +45 -1
- package/src/cli/validate.test.ts +33 -3
- package/src/cli/validate.ts +13 -5
- package/src/manifest/manifest.test.ts +229 -0
- package/src/manifest/manifest.ts +109 -1
- package/src/protocol/bridgeCrash.ts +10 -1
- package/src/protocol/bridgeEvents.ts +2 -0
- package/src/runtime/bridgeDriver.test.ts +24 -0
- package/src/runtime/bridgeDriver.ts +6 -2
- package/src/runtime/bridgeEvents.test.ts +65 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { BotInspectWarning } from "./botInspectWarning";
|
|
3
|
+
import { BotRuntime } from "./botRuntime";
|
|
4
|
+
import { detect } from "./detect";
|
|
5
|
+
import { nodeLtsMajorOf } from "./detectedVersion";
|
|
6
|
+
import { DETECT_TABLE } from "./detectTable";
|
|
7
|
+
|
|
8
|
+
const DISCORD_NODE = {
|
|
9
|
+
"package.json": JSON.stringify({
|
|
10
|
+
name: "discord-bot",
|
|
11
|
+
main: "index.js",
|
|
12
|
+
dependencies: {
|
|
13
|
+
"discord.js": "^14.27.0",
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
"index.js": 'import { Client } from "discord.js";\nawait client.login(process.env.DISCORD_TOKEN);\n',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const resultOf = (files: Record<string, string>) => {
|
|
20
|
+
const contents = {
|
|
21
|
+
...DISCORD_NODE,
|
|
22
|
+
...files,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return detect({
|
|
26
|
+
files: Object.keys(contents).map((path) => ({
|
|
27
|
+
path,
|
|
28
|
+
})),
|
|
29
|
+
contents,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const LTS_CASES: readonly {
|
|
34
|
+
alias: string;
|
|
35
|
+
major: string | null;
|
|
36
|
+
}[] = [
|
|
37
|
+
{
|
|
38
|
+
alias: "lts/iron",
|
|
39
|
+
major: "20",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
alias: "LTS/Hydrogen",
|
|
43
|
+
major: "18",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
alias: "lts/jod",
|
|
47
|
+
major: "22",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
alias: "lts-krypton",
|
|
51
|
+
major: "24",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
alias: " lts/argon ",
|
|
55
|
+
major: "4",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
alias: "lts/*",
|
|
59
|
+
major: null,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
alias: "lts/-1",
|
|
63
|
+
major: null,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
alias: "lts",
|
|
67
|
+
major: null,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
alias: "lts/unknown",
|
|
71
|
+
major: null,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
alias: "lts/constructor",
|
|
75
|
+
major: null,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
alias: "node",
|
|
79
|
+
major: null,
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
describe("nodeLtsMajorOf", () => {
|
|
84
|
+
for (const item of LTS_CASES) {
|
|
85
|
+
it(`reads ${item.alias} as ${item.major ?? "no fixed major"}`, () => {
|
|
86
|
+
expect(nodeLtsMajorOf(item.alias)).toBe(item.major);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("detect", () => {
|
|
92
|
+
it("reads the Node major an .nvmrc names by its LTS codename", () => {
|
|
93
|
+
expect(
|
|
94
|
+
resultOf({
|
|
95
|
+
".nvmrc": "lts/iron\n",
|
|
96
|
+
}).nodeVersion,
|
|
97
|
+
).toBe("20");
|
|
98
|
+
expect(
|
|
99
|
+
resultOf({
|
|
100
|
+
".nvmrc": "LTS/Hydrogen\n",
|
|
101
|
+
}).nodeVersion,
|
|
102
|
+
).toBe("18");
|
|
103
|
+
expect(
|
|
104
|
+
resultOf({
|
|
105
|
+
".nvmrc": "lts/*\n",
|
|
106
|
+
}).nodeVersion,
|
|
107
|
+
).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("warns when an LTS codename names a major the image does not run", () => {
|
|
111
|
+
const result = resultOf({
|
|
112
|
+
".nvmrc": "lts/hydrogen\n",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(DETECT_TABLE.node.defaultVersion).not.toBe("18");
|
|
116
|
+
expect(result.warnings).toContain(BotInspectWarning.NodeVersionUnmet);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("reads the Node version from .tool-versions under mise's node and asdf's nodejs", () => {
|
|
120
|
+
expect(
|
|
121
|
+
resultOf({
|
|
122
|
+
".tool-versions": "node 20.11.0\n",
|
|
123
|
+
}).nodeVersion,
|
|
124
|
+
).toBe("20");
|
|
125
|
+
expect(
|
|
126
|
+
resultOf({
|
|
127
|
+
".tool-versions": "nodejs 20.11.0\n",
|
|
128
|
+
}).nodeVersion,
|
|
129
|
+
).toBe("20");
|
|
130
|
+
expect(
|
|
131
|
+
resultOf({
|
|
132
|
+
".tool-versions": "python 3.12.1\nnode lts/jod\n",
|
|
133
|
+
}).nodeVersion,
|
|
134
|
+
).toBe("22");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("leaves the Python version untouched by a Node LTS codename", () => {
|
|
138
|
+
const result = detect({
|
|
139
|
+
files: [
|
|
140
|
+
{
|
|
141
|
+
path: "requirements.txt",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: "main.py",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
path: ".python-version",
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
contents: {
|
|
151
|
+
"requirements.txt": "python-telegram-bot==21.11.1\n",
|
|
152
|
+
"main.py": "from telegram.ext import Application\n",
|
|
153
|
+
".python-version": "lts/iron\n",
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result.runtime).toBe(BotRuntime.Python);
|
|
158
|
+
expect(result.pythonVersion).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
export const DETECTED_VERSION_PATTERN = /^\d+(?:\.\d+)?$/;
|
|
2
2
|
|
|
3
|
+
const NODE_LTS_MAJORS: ReadonlyMap<string, string> = new Map([
|
|
4
|
+
[
|
|
5
|
+
"argon",
|
|
6
|
+
"4",
|
|
7
|
+
],
|
|
8
|
+
[
|
|
9
|
+
"boron",
|
|
10
|
+
"6",
|
|
11
|
+
],
|
|
12
|
+
[
|
|
13
|
+
"carbon",
|
|
14
|
+
"8",
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"dubnium",
|
|
18
|
+
"10",
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
"erbium",
|
|
22
|
+
"12",
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
"fermium",
|
|
26
|
+
"14",
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
"gallium",
|
|
30
|
+
"16",
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"hydrogen",
|
|
34
|
+
"18",
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
"iron",
|
|
38
|
+
"20",
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"jod",
|
|
42
|
+
"22",
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
"krypton",
|
|
46
|
+
"24",
|
|
47
|
+
],
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const NODE_LTS_ALIAS = /^lts[/-]([a-z]+)$/i;
|
|
51
|
+
|
|
3
52
|
export const isDetectedVersion = (value: string | null): value is string => {
|
|
4
53
|
return value !== null && DETECTED_VERSION_PATTERN.test(value);
|
|
5
54
|
};
|
|
55
|
+
|
|
56
|
+
export const nodeLtsMajorOf = (alias: string): string | null => {
|
|
57
|
+
const codename = NODE_LTS_ALIAS.exec(alias.trim())?.[1];
|
|
58
|
+
|
|
59
|
+
return codename === undefined ? null : (NODE_LTS_MAJORS.get(codename.toLowerCase()) ?? null);
|
|
60
|
+
};
|
|
@@ -33,6 +33,116 @@ describe("scanEnvKeys", () => {
|
|
|
33
33
|
]);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
it("finds a Bun.env property read", () => {
|
|
37
|
+
expect(scanEnvKeys("src/index.ts", "const token = Bun.env.DISCORD_TOKEN;")).toEqual([
|
|
38
|
+
"DISCORD_TOKEN",
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("finds a Bun.env bracket read", () => {
|
|
43
|
+
expect(scanEnvKeys("src/index.ts", 'const guildId = Bun.env["GUILD_ID"] ?? "";')).toEqual([
|
|
44
|
+
"GUILD_ID",
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("finds names destructured from Bun.env", () => {
|
|
49
|
+
expect(scanEnvKeys("src/config.ts", "const { DISCORD_TOKEN, CLIENT_ID } = Bun.env;")).toEqual([
|
|
50
|
+
"DISCORD_TOKEN",
|
|
51
|
+
"CLIENT_ID",
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("finds environs reads, bare and typed", () => {
|
|
56
|
+
expect(
|
|
57
|
+
scanEnvKeys(
|
|
58
|
+
"tgbot/config.py",
|
|
59
|
+
[
|
|
60
|
+
"from environs import Env",
|
|
61
|
+
"",
|
|
62
|
+
"env = Env()",
|
|
63
|
+
"env.read_env(path)",
|
|
64
|
+
'BOT_TOKEN = env.str("BOT_TOKEN")',
|
|
65
|
+
'ADMIN_ID = env.int("ADMIN_ID")',
|
|
66
|
+
'admin_ids = env.list("ADMINS", subcast=int)',
|
|
67
|
+
'use_redis = env.bool("USE_REDIS")',
|
|
68
|
+
'rate = env.float("RATE_LIMIT", 1.5)',
|
|
69
|
+
'webhook = env.url("WEBHOOK_URL")',
|
|
70
|
+
'extra = env.json("EXTRA_OPTIONS")',
|
|
71
|
+
"SECRET_KEY = env('SECRET_KEY')",
|
|
72
|
+
].join("\n"),
|
|
73
|
+
),
|
|
74
|
+
).toEqual([
|
|
75
|
+
"BOT_TOKEN",
|
|
76
|
+
"ADMIN_ID",
|
|
77
|
+
"ADMINS",
|
|
78
|
+
"USE_REDIS",
|
|
79
|
+
"RATE_LIMIT",
|
|
80
|
+
"WEBHOOK_URL",
|
|
81
|
+
"EXTRA_OPTIONS",
|
|
82
|
+
"SECRET_KEY",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("finds python-decouple reads", () => {
|
|
87
|
+
expect(
|
|
88
|
+
scanEnvKeys(
|
|
89
|
+
"bot/settings.py",
|
|
90
|
+
[
|
|
91
|
+
"import decouple",
|
|
92
|
+
"from decouple import config",
|
|
93
|
+
"",
|
|
94
|
+
'TOKEN = config("TOKEN")',
|
|
95
|
+
'ADMIN_ID = config("ADMIN_ID", cast=int)',
|
|
96
|
+
'DEBUG = decouple.config("DEBUG", default=False, cast=bool)',
|
|
97
|
+
].join("\n"),
|
|
98
|
+
),
|
|
99
|
+
).toEqual([
|
|
100
|
+
"TOKEN",
|
|
101
|
+
"ADMIN_ID",
|
|
102
|
+
"DEBUG",
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("orders names by where the file reads them across os and environs", () => {
|
|
107
|
+
expect(
|
|
108
|
+
scanEnvKeys(
|
|
109
|
+
"bot/config.py",
|
|
110
|
+
[
|
|
111
|
+
"import os",
|
|
112
|
+
"from environs import Env",
|
|
113
|
+
"",
|
|
114
|
+
"env = Env()",
|
|
115
|
+
'BOT_TOKEN = env.str("BOT_TOKEN")',
|
|
116
|
+
'REDIS_URL = os.getenv("REDIS_URL")',
|
|
117
|
+
'ADMIN_ID = env.int("ADMIN_ID")',
|
|
118
|
+
].join("\n"),
|
|
119
|
+
),
|
|
120
|
+
).toEqual([
|
|
121
|
+
"BOT_TOKEN",
|
|
122
|
+
"REDIS_URL",
|
|
123
|
+
"ADMIN_ID",
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("reads no key from calls that only look like environs or decouple", () => {
|
|
128
|
+
expect(
|
|
129
|
+
scanEnvKeys(
|
|
130
|
+
"bot/main.py",
|
|
131
|
+
[
|
|
132
|
+
"env = {}",
|
|
133
|
+
'env["x"] = 1',
|
|
134
|
+
'value = env["x"]',
|
|
135
|
+
'logger.config("level")',
|
|
136
|
+
'logger.config("LEVEL")',
|
|
137
|
+
'config("level")',
|
|
138
|
+
'self.env.str("NOPE")',
|
|
139
|
+
'client.getenv("NOPE")',
|
|
140
|
+
'cfg.environ.get("NOPE")',
|
|
141
|
+
].join("\n"),
|
|
142
|
+
),
|
|
143
|
+
).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
|
|
36
146
|
it("reads nothing from files that are not code", () => {
|
|
37
147
|
expect(scanEnvKeys("README.md", "process.env.A")).toEqual([]);
|
|
38
148
|
});
|
|
@@ -13,12 +13,14 @@ export const PYTHON_SOURCE_EXTENSIONS: readonly string[] = [
|
|
|
13
13
|
|
|
14
14
|
const NAME = "([A-Za-z_][A-Za-z0-9_]*)";
|
|
15
15
|
|
|
16
|
+
const UPPER_NAME = "([A-Z_][A-Z0-9_]*)";
|
|
17
|
+
|
|
16
18
|
const NODE_PATTERNS: readonly RegExp[] = [
|
|
17
|
-
new RegExp(`\\
|
|
18
|
-
new RegExp(`\\
|
|
19
|
+
new RegExp(`\\b(?:process|Bun)\\.env\\.${NAME}`, "g"),
|
|
20
|
+
new RegExp(`\\b(?:process|Bun)\\.env\\[\\s*["'\`]${NAME}["'\`]\\s*\\]`, "g"),
|
|
19
21
|
];
|
|
20
22
|
|
|
21
|
-
const NODE_DESTRUCTURE = /\{([^{}]{1,2000})\}\s*=\s*process\.env\b/g;
|
|
23
|
+
const NODE_DESTRUCTURE = /\{([^{}]{1,2000})\}\s*=\s*(?:process|Bun)\.env\b/g;
|
|
22
24
|
|
|
23
25
|
const DESTRUCTURED_NAME = /^\s*([A-Za-z_][A-Za-z0-9_]*)/;
|
|
24
26
|
|
|
@@ -26,6 +28,8 @@ const PYTHON_PATTERNS: readonly RegExp[] = [
|
|
|
26
28
|
new RegExp(`(?<![\\w.])(?:os\\.)?environ\\[\\s*["']${NAME}["']\\s*\\]`, "g"),
|
|
27
29
|
new RegExp(`(?<![\\w.])(?:os\\.)?environ\\.get\\(\\s*["']${NAME}["']`, "g"),
|
|
28
30
|
new RegExp(`(?<![\\w.])(?:os\\.)?getenv\\(\\s*["']${NAME}["']`, "g"),
|
|
31
|
+
new RegExp(`(?<![\\w.])env(?:\\.\\w+)?\\(\\s*["']${UPPER_NAME}["']`, "g"),
|
|
32
|
+
new RegExp(`(?<![\\w.])(?:decouple\\.)?config\\(\\s*["']${UPPER_NAME}["']`, "g"),
|
|
29
33
|
];
|
|
30
34
|
|
|
31
35
|
interface Found {
|
|
@@ -3,9 +3,21 @@ import {
|
|
|
3
3
|
BOT_AMBIENT_KEYS,
|
|
4
4
|
BOT_PLATFORM_KEYS,
|
|
5
5
|
BOT_PLATFORM_SECRET_KEYS,
|
|
6
|
+
BOT_PORT_1,
|
|
7
|
+
BOT_PORT_1_KEY,
|
|
8
|
+
BOT_PORT_2,
|
|
9
|
+
BOT_PORT_2_KEY,
|
|
6
10
|
BOT_PROTECTED_KEYS,
|
|
7
11
|
BOT_PROVIDED_KEYS,
|
|
12
|
+
BOT_RAW_PORT_KEYS,
|
|
13
|
+
BOT_WEB,
|
|
14
|
+
BOT_WEB_PORT_KEY,
|
|
8
15
|
isReservedBotKey,
|
|
16
|
+
PORT,
|
|
17
|
+
SERVERK_PORT_1,
|
|
18
|
+
SERVERK_PORT_2,
|
|
19
|
+
SERVERK_WEB_HOST,
|
|
20
|
+
SERVERK_WEB_URL,
|
|
9
21
|
} from "./keys";
|
|
10
22
|
|
|
11
23
|
describe("isReservedBotKey", () => {
|
|
@@ -21,6 +33,21 @@ describe("isReservedBotKey", () => {
|
|
|
21
33
|
}
|
|
22
34
|
});
|
|
23
35
|
|
|
36
|
+
it("reserves the web and port switches the platform sets", () => {
|
|
37
|
+
for (const key of [
|
|
38
|
+
BOT_WEB,
|
|
39
|
+
BOT_PORT_1,
|
|
40
|
+
BOT_PORT_2,
|
|
41
|
+
]) {
|
|
42
|
+
expect(BOT_PLATFORM_KEYS).toContain(key);
|
|
43
|
+
expect(isReservedBotKey(key)).toBe(true);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("leaves PORT to the customer, so a bot without the web port keeps its own", () => {
|
|
48
|
+
expect(isReservedBotKey(PORT)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
24
51
|
it("leaves the customer's own keys alone, case included", () => {
|
|
25
52
|
for (const key of [
|
|
26
53
|
"DISCORD_TOKEN",
|
|
@@ -47,6 +74,19 @@ describe("BOT_AMBIENT_KEYS", () => {
|
|
|
47
74
|
}
|
|
48
75
|
});
|
|
49
76
|
|
|
77
|
+
it("holds the web address and raw port variables, so detection never asks for them", () => {
|
|
78
|
+
for (const key of [
|
|
79
|
+
SERVERK_WEB_URL,
|
|
80
|
+
SERVERK_WEB_HOST,
|
|
81
|
+
SERVERK_PORT_1,
|
|
82
|
+
SERVERK_PORT_2,
|
|
83
|
+
]) {
|
|
84
|
+
expect(BOT_PROVIDED_KEYS).toContain(key);
|
|
85
|
+
expect(BOT_AMBIENT_KEYS).toContain(key);
|
|
86
|
+
expect(isReservedBotKey(key)).toBe(true);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
50
90
|
it("leaves a bot's own keys out", () => {
|
|
51
91
|
for (const key of [
|
|
52
92
|
"DISCORD_TOKEN",
|
|
@@ -57,3 +97,13 @@ describe("BOT_AMBIENT_KEYS", () => {
|
|
|
57
97
|
}
|
|
58
98
|
});
|
|
59
99
|
});
|
|
100
|
+
|
|
101
|
+
describe("BOT_RAW_PORT_KEYS", () => {
|
|
102
|
+
it("names the two raw ports apart from the web port", () => {
|
|
103
|
+
expect(BOT_RAW_PORT_KEYS).toEqual([
|
|
104
|
+
BOT_PORT_1_KEY,
|
|
105
|
+
BOT_PORT_2_KEY,
|
|
106
|
+
]);
|
|
107
|
+
expect(BOT_RAW_PORT_KEYS).not.toContain(BOT_WEB_PORT_KEY);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/src/bot/detect/keys.ts
CHANGED
|
@@ -18,12 +18,49 @@ export const SNAPSHOT_ID = "SNAPSHOT_ID";
|
|
|
18
18
|
|
|
19
19
|
export const BOT_PLATFORM = "BOT_PLATFORM";
|
|
20
20
|
|
|
21
|
+
export const BOT_WEB = "BOT_WEB";
|
|
22
|
+
|
|
23
|
+
export const BOT_PORT_1 = "BOT_PORT_1";
|
|
24
|
+
|
|
25
|
+
export const BOT_PORT_2 = "BOT_PORT_2";
|
|
26
|
+
|
|
27
|
+
export const BOT_SWITCH_ON = "on";
|
|
28
|
+
|
|
21
29
|
export const GIT_TOKEN = "GIT_TOKEN";
|
|
22
30
|
|
|
23
31
|
export const SERVERK_BOT = "SERVERK_BOT";
|
|
24
32
|
|
|
25
33
|
export const SERVERK_SERVER_CODE = "SERVERK_SERVER_CODE";
|
|
26
34
|
|
|
35
|
+
export const SERVERK_WEB_URL = "SERVERK_WEB_URL";
|
|
36
|
+
|
|
37
|
+
export const SERVERK_WEB_HOST = "SERVERK_WEB_HOST";
|
|
38
|
+
|
|
39
|
+
export const SERVERK_PORT_1 = "SERVERK_PORT_1";
|
|
40
|
+
|
|
41
|
+
export const SERVERK_PORT_2 = "SERVERK_PORT_2";
|
|
42
|
+
|
|
43
|
+
export const PORT = "PORT";
|
|
44
|
+
|
|
45
|
+
export const BOT_WEB_PORT_KEY = "web";
|
|
46
|
+
|
|
47
|
+
export const BOT_PORT_1_KEY = "port1";
|
|
48
|
+
|
|
49
|
+
export const BOT_PORT_2_KEY = "port2";
|
|
50
|
+
|
|
51
|
+
export const BOT_RAW_PORT_KEYS = [
|
|
52
|
+
BOT_PORT_1_KEY,
|
|
53
|
+
BOT_PORT_2_KEY,
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
export type BotRawPortKey = (typeof BOT_RAW_PORT_KEYS)[number];
|
|
57
|
+
|
|
58
|
+
export const BOT_PORT_SWITCHES = {
|
|
59
|
+
[BOT_WEB_PORT_KEY]: BOT_WEB,
|
|
60
|
+
[BOT_PORT_1_KEY]: BOT_PORT_1,
|
|
61
|
+
[BOT_PORT_2_KEY]: BOT_PORT_2,
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
27
64
|
export const BOT_PLATFORM_KEYS: readonly string[] = [
|
|
28
65
|
REPO_URL,
|
|
29
66
|
REPO_REF,
|
|
@@ -34,6 +71,9 @@ export const BOT_PLATFORM_KEYS: readonly string[] = [
|
|
|
34
71
|
DEPLOYMENT_ID,
|
|
35
72
|
SNAPSHOT_ID,
|
|
36
73
|
BOT_PLATFORM,
|
|
74
|
+
BOT_WEB,
|
|
75
|
+
BOT_PORT_1,
|
|
76
|
+
BOT_PORT_2,
|
|
37
77
|
];
|
|
38
78
|
|
|
39
79
|
export const BOT_PLATFORM_SECRET_KEYS: readonly string[] = [
|
|
@@ -52,6 +92,10 @@ export const BOT_PROVIDED_KEYS: readonly string[] = [
|
|
|
52
92
|
SERVERK_BOT,
|
|
53
93
|
SERVERK_SERVER_CODE,
|
|
54
94
|
BOT_PLATFORM,
|
|
95
|
+
SERVERK_WEB_URL,
|
|
96
|
+
SERVERK_WEB_HOST,
|
|
97
|
+
SERVERK_PORT_1,
|
|
98
|
+
SERVERK_PORT_2,
|
|
55
99
|
];
|
|
56
100
|
|
|
57
101
|
export const BOT_AMBIENT_KEYS: readonly string[] = [
|
|
@@ -59,7 +103,7 @@ export const BOT_AMBIENT_KEYS: readonly string[] = [
|
|
|
59
103
|
...BOT_PROVIDED_KEYS,
|
|
60
104
|
"NODE_ENV",
|
|
61
105
|
"NODE_OPTIONS",
|
|
62
|
-
|
|
106
|
+
PORT,
|
|
63
107
|
"HOST",
|
|
64
108
|
"HOSTNAME",
|
|
65
109
|
"DEBUG",
|
package/src/cli/validate.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { renderDetectTable } from "../bot/detect";
|
|
5
|
+
import { BOT_SWITCH_ON, BOT_WEB, BOT_WEB_PORT_KEY, renderDetectTable } from "../bot/detect";
|
|
6
6
|
import { BridgeKind, BridgeSetupStepKind } from "../core";
|
|
7
7
|
import { GuideOpenTab, type GuidePack, type ParsedBlock, type ParsedLeafBlock } from "../guides";
|
|
8
8
|
import { MANIFEST_SCHEMA_VERSION, PackageKind, type RunnableManifest } from "../manifest";
|
|
@@ -600,7 +600,7 @@ describe("validating a bot package", () => {
|
|
|
600
600
|
);
|
|
601
601
|
});
|
|
602
602
|
|
|
603
|
-
it("warns about a port
|
|
603
|
+
it("warns about a bot port with no activeWhen but still validates it", async () => {
|
|
604
604
|
writeBot(
|
|
605
605
|
botManifest({
|
|
606
606
|
container: {
|
|
@@ -616,10 +616,40 @@ describe("validating a bot package", () => {
|
|
|
616
616
|
|
|
617
617
|
expect(lines).toContain("bot-node is valid");
|
|
618
618
|
expect(lines).toContain(
|
|
619
|
-
" warning: a bot package declares 1 port(s);
|
|
619
|
+
" warning: a bot package declares 1 port(s) with no activeWhen; a bot publishes a port only while its author turns it on",
|
|
620
620
|
);
|
|
621
621
|
});
|
|
622
622
|
|
|
623
|
+
it("validates a bot web port its author turns on without a warning", async () => {
|
|
624
|
+
writeBot(
|
|
625
|
+
botManifest({
|
|
626
|
+
container: {
|
|
627
|
+
image: "serverkgg/bot-node",
|
|
628
|
+
ports: [
|
|
629
|
+
{
|
|
630
|
+
...WEBHOOK_PORT,
|
|
631
|
+
key: BOT_WEB_PORT_KEY,
|
|
632
|
+
web: true,
|
|
633
|
+
activeWhen: [
|
|
634
|
+
{
|
|
635
|
+
variable: BOT_WEB,
|
|
636
|
+
values: [
|
|
637
|
+
BOT_SWITCH_ON,
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
},
|
|
642
|
+
],
|
|
643
|
+
},
|
|
644
|
+
}),
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
await validate();
|
|
648
|
+
|
|
649
|
+
expect(lines).toContain("bot-node is valid");
|
|
650
|
+
expect(lines.some((line) => line.includes("warning:"))).toBe(false);
|
|
651
|
+
});
|
|
652
|
+
|
|
623
653
|
it("fails a bot that declares platform secrets", async () => {
|
|
624
654
|
writeBot(
|
|
625
655
|
botManifest({
|
package/src/cli/validate.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { type ArticlePack, compileArticles } from "../blog";
|
|
7
|
-
import { DETECT_TABLE_FILE, renderDetectTable } from "../bot/detect";
|
|
7
|
+
import { BOT_PORT_SWITCHES, DETECT_TABLE_FILE, renderDetectTable } from "../bot/detect";
|
|
8
8
|
import { BridgeKind, PortProtocol } from "../core";
|
|
9
9
|
import {
|
|
10
10
|
GuideOpenTab,
|
|
@@ -599,14 +599,18 @@ export const setupReportLines = (setup: BridgeSetupModule | undefined): string[]
|
|
|
599
599
|
};
|
|
600
600
|
|
|
601
601
|
export const botPortLines = (manifest: RunnableManifest): string[] => {
|
|
602
|
-
|
|
602
|
+
if (!isBotManifest(manifest)) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
603
605
|
|
|
604
|
-
|
|
606
|
+
const ungated = manifest.container.ports.filter((port) => port.activeWhen.length === 0).length;
|
|
607
|
+
|
|
608
|
+
if (ungated === 0) {
|
|
605
609
|
return [];
|
|
606
610
|
}
|
|
607
611
|
|
|
608
612
|
return [
|
|
609
|
-
` warning: a bot package declares ${
|
|
613
|
+
` warning: a bot package declares ${ungated} port(s) with no activeWhen; a bot publishes a port only while its author turns it on`,
|
|
610
614
|
];
|
|
611
615
|
};
|
|
612
616
|
|
|
@@ -680,7 +684,11 @@ const validateRunnablePackage = async (
|
|
|
680
684
|
}
|
|
681
685
|
|
|
682
686
|
validateDriver(driver);
|
|
683
|
-
validatePortActivation(
|
|
687
|
+
validatePortActivation(
|
|
688
|
+
manifest.container.ports,
|
|
689
|
+
driver,
|
|
690
|
+
isBotManifest(manifest) ? Object.values(BOT_PORT_SWITCHES) : [],
|
|
691
|
+
);
|
|
684
692
|
|
|
685
693
|
const confirmLines = confirmReportLines(driver);
|
|
686
694
|
|