@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
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
".python-version": "3.13\n",
|
|
28
28
|
"Procfile": "worker: bash start\n",
|
|
29
29
|
"pyproject.toml": "[project]\nname = \"anonxmusic\"\nversion = \"3.0.3\"\ndescription = \"Telegram Group Calls Streaming bot with some useful features.\"\nreadme = \".github/README.md\"\nrequires-python = \">=3.10\"\n\ndependencies = [\n \"aiohttp~=3.14.3\",\n \"kurigram>=2.2.25\",\n \"ntgcalls==3.0.0rc1\",\n \"pillow~=12.3.0\",\n \"psutil~=7.2.2\",\n \"py-tgcalls==3.0.0rc1\",\n \"py-yt-search~=0.8.0\",\n \"pymongo>=4.17.0\",\n \"pytgcrypto>=1.2.12\",\n \"python-dotenv~=1.2.3\",\n \"yt-dlp[default]>=2026.8.19\",\n]\n",
|
|
30
|
-
"requirements.txt": "aiohttp~=3.14.3\nkurigram>=2.2.25\nntgcalls==3.0.0rc1\npillow~=12.3.0\npsutil~=7.2.2\npy-tgcalls==3.0.0rc1\npy-yt-search~=0.8.0\npymongo>=4.17.0\npytgcrypto>=1.2.12\npython-dotenv~=1.2.3\nyt-dlp[default]>=2026.8.19\n"
|
|
30
|
+
"requirements.txt": "aiohttp~=3.14.3\nkurigram>=2.2.25\nntgcalls==3.0.0rc1\npillow~=12.3.0\npsutil~=7.2.2\npy-tgcalls==3.0.0rc1\npy-yt-search~=0.8.0\npymongo>=4.17.0\npytgcrypto>=1.2.12\npython-dotenv~=1.2.3\nyt-dlp[default]>=2026.8.19\n",
|
|
31
|
+
"sample.env": "# from my.telegram.org/apps\nAPI_ID=\nAPI_HASH=\n\n# from @BotFather on telegram\nBOT_TOKEN=\nLOGGER_ID=\n\n# mongo url from cloud.mongodb.com\nMONGO_URL=\n\nOWNER_ID=\n\n# pyrogram session from @StringFatherBot on telegram\nSESSION=\n"
|
|
31
32
|
},
|
|
32
33
|
"expected": {
|
|
33
34
|
"runtime": "python",
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
"start": "bash start",
|
|
40
41
|
"nodeVersion": null,
|
|
41
42
|
"pythonVersion": "3.13",
|
|
42
|
-
"tokenKey":
|
|
43
|
+
"tokenKey": "BOT_TOKEN",
|
|
43
44
|
"warnings": []
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": {
|
|
3
|
-
"url": "https://github.com/serverkgg/bot-
|
|
3
|
+
"url": "https://github.com/serverkgg/bot-template-discord-node",
|
|
4
4
|
"branch": "main",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "0c950227f6c2ab97b610080f2e9c9f60ff8c6069",
|
|
6
6
|
"stars": null
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
".env.example",
|
|
10
10
|
".gitignore",
|
|
11
|
+
"AGENTS.md",
|
|
11
12
|
"LICENSE",
|
|
12
13
|
"README.md",
|
|
13
14
|
"index.js",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": {
|
|
3
|
-
"url": "https://github.com/serverkgg/bot-
|
|
3
|
+
"url": "https://github.com/serverkgg/bot-template-discord-python",
|
|
4
4
|
"branch": "main",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "e520ae1dfb07bb1e8a1eba73c5651a3ad3d39ad1",
|
|
6
6
|
"stars": null
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
".env.example",
|
|
10
10
|
".gitignore",
|
|
11
|
+
"AGENTS.md",
|
|
11
12
|
"LICENSE",
|
|
12
13
|
"README.md",
|
|
13
14
|
"main.py",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": {
|
|
3
|
-
"url": "https://github.com/serverkgg/bot-
|
|
3
|
+
"url": "https://github.com/serverkgg/bot-template-telegram-node",
|
|
4
4
|
"branch": "main",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "49a55385c7336551dbb50b6ebc99a4e18743418f",
|
|
6
6
|
"stars": null
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
".env.example",
|
|
10
10
|
".gitignore",
|
|
11
|
+
"AGENTS.md",
|
|
11
12
|
"LICENSE",
|
|
12
13
|
"README.md",
|
|
13
14
|
"index.js",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": {
|
|
3
|
-
"url": "https://github.com/serverkgg/bot-
|
|
3
|
+
"url": "https://github.com/serverkgg/bot-template-telegram-python",
|
|
4
4
|
"branch": "main",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "342e61c43f02f469c572f43cd3ed1e95ba85a130",
|
|
6
6
|
"stars": null
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
".env.example",
|
|
10
10
|
".gitignore",
|
|
11
|
+
"AGENTS.md",
|
|
11
12
|
"LICENSE",
|
|
12
13
|
"README.md",
|
|
13
14
|
"main.py",
|
|
@@ -51,6 +51,16 @@ const TELEGRAM_PYTHON = {
|
|
|
51
51
|
".env.example": "TELEGRAM_TOKEN=\n",
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
const NPM_INSTALLED_PACKAGE = `${JSON.stringify(
|
|
55
|
+
{
|
|
56
|
+
dependencies: {
|
|
57
|
+
"discord.js": "^14.27.0",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
null,
|
|
61
|
+
2,
|
|
62
|
+
)}\n`;
|
|
63
|
+
|
|
54
64
|
describe("detect on the templates", () => {
|
|
55
65
|
it("reads the discord.js template end to end", () => {
|
|
56
66
|
expect(
|
|
@@ -371,6 +381,89 @@ describe("detect on the templates", () => {
|
|
|
371
381
|
expect(project(TELEGRAM_PYTHON).pythonVersion).toBeNull();
|
|
372
382
|
});
|
|
373
383
|
|
|
384
|
+
const NODE_VERSION_CASES: readonly {
|
|
385
|
+
first: string;
|
|
386
|
+
second: string;
|
|
387
|
+
files: Record<string, string>;
|
|
388
|
+
version: string;
|
|
389
|
+
}[] = [
|
|
390
|
+
{
|
|
391
|
+
first: "engines.node",
|
|
392
|
+
second: ".nvmrc",
|
|
393
|
+
files: {
|
|
394
|
+
"package.json": JSON.stringify({
|
|
395
|
+
...JSON.parse(DISCORD_NODE["package.json"]),
|
|
396
|
+
engines: {
|
|
397
|
+
node: ">=18",
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
400
|
+
".nvmrc": "v20.11.1\n",
|
|
401
|
+
},
|
|
402
|
+
version: "18",
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
first: ".nvmrc",
|
|
406
|
+
second: ".node-version",
|
|
407
|
+
files: {
|
|
408
|
+
".nvmrc": "v22.11.0\n",
|
|
409
|
+
".node-version": "20.11.1\n",
|
|
410
|
+
},
|
|
411
|
+
version: "22",
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
first: ".node-version",
|
|
415
|
+
second: ".tool-versions",
|
|
416
|
+
files: {
|
|
417
|
+
".node-version": "20.11.1\n",
|
|
418
|
+
".tool-versions": "nodejs 22.3.0\n",
|
|
419
|
+
},
|
|
420
|
+
version: "20",
|
|
421
|
+
},
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
for (const item of NODE_VERSION_CASES) {
|
|
425
|
+
it(`reads the Node version from ${item.first} before ${item.second}`, () => {
|
|
426
|
+
const result = project({
|
|
427
|
+
...DISCORD_NODE,
|
|
428
|
+
...item.files,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(result.nodeVersion).toBe(item.version);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
it("reads the Python version from runtime.txt before the Pipfile's python_version", () => {
|
|
436
|
+
const result = project({
|
|
437
|
+
Pipfile:
|
|
438
|
+
'[[source]]\nurl = "https://pypi.org/simple"\nverify_ssl = true\nname = "pypi"\n\n[packages]\n"discord.py" = "*"\n\n[dev-packages]\n\n[requires]\npython_version = "3.10"\n',
|
|
439
|
+
"runtime.txt": "python-3.11.9\n",
|
|
440
|
+
"bot.py": "import discord\n",
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
expect(result.packageManager).toBe(BotPackageManager.Pipenv);
|
|
444
|
+
expect(result.pythonVersion).toBe("3.11");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("installs with yarn when only engines.yarn names a manager and no lockfile ships", () => {
|
|
448
|
+
const result = project({
|
|
449
|
+
...DISCORD_NODE,
|
|
450
|
+
"package.json": JSON.stringify({
|
|
451
|
+
...JSON.parse(DISCORD_NODE["package.json"]),
|
|
452
|
+
engines: {
|
|
453
|
+
node: ">=18",
|
|
454
|
+
yarn: ">=1.22",
|
|
455
|
+
},
|
|
456
|
+
}),
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(result.packageManager).toBe(BotPackageManager.Yarn);
|
|
460
|
+
expect(result.install).toEqual({
|
|
461
|
+
command: "corepack yarn install",
|
|
462
|
+
source: "package.json",
|
|
463
|
+
});
|
|
464
|
+
expect(result.warnings).toContain(BotInspectWarning.NoLockfile);
|
|
465
|
+
});
|
|
466
|
+
|
|
374
467
|
it("installs from the Python lockfiles, honouring uv and warning about the rest", () => {
|
|
375
468
|
const locked = (lockfile: string) => {
|
|
376
469
|
return project(
|
|
@@ -484,6 +577,107 @@ describe("detect start commands", () => {
|
|
|
484
577
|
});
|
|
485
578
|
});
|
|
486
579
|
|
|
580
|
+
const NODE_START_CASES: readonly {
|
|
581
|
+
file: string;
|
|
582
|
+
beside: string[];
|
|
583
|
+
}[] = [
|
|
584
|
+
{
|
|
585
|
+
file: "index.js",
|
|
586
|
+
beside: [
|
|
587
|
+
"src/index.js",
|
|
588
|
+
"bot.js",
|
|
589
|
+
"src/bot.js",
|
|
590
|
+
"index.ts",
|
|
591
|
+
],
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
file: "src/index.js",
|
|
595
|
+
beside: [
|
|
596
|
+
"bot.js",
|
|
597
|
+
"src/bot.js",
|
|
598
|
+
"index.ts",
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
file: "bot.js",
|
|
603
|
+
beside: [
|
|
604
|
+
"src/bot.js",
|
|
605
|
+
"index.ts",
|
|
606
|
+
],
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
file: "src/bot.js",
|
|
610
|
+
beside: [
|
|
611
|
+
"index.ts",
|
|
612
|
+
],
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
file: "index.ts",
|
|
616
|
+
beside: [],
|
|
617
|
+
},
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
for (const item of NODE_START_CASES) {
|
|
621
|
+
it(`starts ${item.file} when package.json names no start script and no main, ahead of [${item.beside.join(", ")}]`, () => {
|
|
622
|
+
const result = project(
|
|
623
|
+
{
|
|
624
|
+
"package.json": NPM_INSTALLED_PACKAGE,
|
|
625
|
+
[item.file]: "",
|
|
626
|
+
},
|
|
627
|
+
item.beside,
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
expect(result.start).toEqual({
|
|
631
|
+
command: `node ${item.file}`,
|
|
632
|
+
source: item.file,
|
|
633
|
+
});
|
|
634
|
+
expect(result.warnings).toEqual([
|
|
635
|
+
BotInspectWarning.NoLockfile,
|
|
636
|
+
]);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const PYTHON_START_CASES: readonly {
|
|
641
|
+
file: string;
|
|
642
|
+
beside: string[];
|
|
643
|
+
}[] = [
|
|
644
|
+
{
|
|
645
|
+
file: "app.py",
|
|
646
|
+
beside: [
|
|
647
|
+
"src/main.py",
|
|
648
|
+
"src/bot.py",
|
|
649
|
+
],
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
file: "src/main.py",
|
|
653
|
+
beside: [
|
|
654
|
+
"src/bot.py",
|
|
655
|
+
],
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
file: "src/bot.py",
|
|
659
|
+
beside: [],
|
|
660
|
+
},
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
for (const item of PYTHON_START_CASES) {
|
|
664
|
+
it(`starts ${item.file} when no main.py or bot.py exists, ahead of [${item.beside.join(", ")}]`, () => {
|
|
665
|
+
const result = project(
|
|
666
|
+
{
|
|
667
|
+
"requirements.txt": "python-telegram-bot==21.11.1\n",
|
|
668
|
+
[item.file]: "",
|
|
669
|
+
},
|
|
670
|
+
item.beside,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
expect(result.start).toEqual({
|
|
674
|
+
command: `python ${item.file}`,
|
|
675
|
+
source: item.file,
|
|
676
|
+
});
|
|
677
|
+
expect(result.warnings).toEqual([]);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
487
681
|
it("starts the one Python package that has a __main__ module when no start file exists", () => {
|
|
488
682
|
expect(
|
|
489
683
|
project(
|
|
@@ -678,6 +872,90 @@ describe("detect warnings", () => {
|
|
|
678
872
|
|
|
679
873
|
expect(quiet.warnings).not.toContain(BotInspectWarning.ReservedEnvKey);
|
|
680
874
|
});
|
|
875
|
+
|
|
876
|
+
it("keeps quiet about the web address and raw port variables an example file lists, and warns about the switches", () => {
|
|
877
|
+
const quiet = project({
|
|
878
|
+
...DISCORD_NODE,
|
|
879
|
+
".env.example": "DISCORD_TOKEN=\nSERVERK_WEB_URL=\nSERVERK_WEB_HOST=\nSERVERK_PORT_1=\nSERVERK_PORT_2=\n",
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
expect(quiet.env.map((entry) => entry.key)).toEqual([
|
|
883
|
+
"DISCORD_TOKEN",
|
|
884
|
+
]);
|
|
885
|
+
expect(quiet.warnings).not.toContain(BotInspectWarning.ReservedEnvKey);
|
|
886
|
+
|
|
887
|
+
const loud = project({
|
|
888
|
+
...DISCORD_NODE,
|
|
889
|
+
".env.example": "DISCORD_TOKEN=\nBOT_WEB=on\n",
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
expect(loud.env.map((entry) => entry.key)).toEqual([
|
|
893
|
+
"DISCORD_TOKEN",
|
|
894
|
+
]);
|
|
895
|
+
expect(loud.warnings).toContain(BotInspectWarning.ReservedEnvKey);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("warns that a node bot reading PORT needs the web address, and keeps PORT among the variables", () => {
|
|
899
|
+
const result = project({
|
|
900
|
+
...DISCORD_NODE,
|
|
901
|
+
"index.js": "const token = process.env.DISCORD_TOKEN;\napp.listen(process.env.PORT);\n",
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
expect(result.env.map((entry) => entry.key)).toEqual([
|
|
905
|
+
"DISCORD_TOKEN",
|
|
906
|
+
"PORT",
|
|
907
|
+
]);
|
|
908
|
+
expect(result.warnings).toContain(BotInspectWarning.WebPort);
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
it("warns that a python bot whose example file lists a webhook needs the web address", () => {
|
|
912
|
+
const result = project({
|
|
913
|
+
...TELEGRAM_PYTHON,
|
|
914
|
+
".env.example": "TELEGRAM_TOKEN=\nWEBHOOK_URL=\n",
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
expect(result.env.map((entry) => entry.key)).toEqual([
|
|
918
|
+
"TELEGRAM_TOKEN",
|
|
919
|
+
"WEBHOOK_URL",
|
|
920
|
+
]);
|
|
921
|
+
expect(result.warnings).toContain(BotInspectWarning.WebPort);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it("reads a webhook key on a discord bot as an outgoing log webhook, and on a telegram bot as one it receives", () => {
|
|
925
|
+
const discord = project({
|
|
926
|
+
...DISCORD_NODE,
|
|
927
|
+
".env.example": "DISCORD_TOKEN=\nLOG_WEBHOOK_URL=\n",
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
expect(discord.platform).toBe(BotPlatform.Discord);
|
|
931
|
+
expect(discord.env.map((entry) => entry.key)).toEqual([
|
|
932
|
+
"DISCORD_TOKEN",
|
|
933
|
+
"LOG_WEBHOOK_URL",
|
|
934
|
+
]);
|
|
935
|
+
expect(discord.warnings).not.toContain(BotInspectWarning.WebPort);
|
|
936
|
+
|
|
937
|
+
const telegram = project({
|
|
938
|
+
...TELEGRAM_PYTHON,
|
|
939
|
+
".env.example": "TELEGRAM_TOKEN=\nLOG_WEBHOOK_URL=\n",
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
expect(telegram.platform).toBe(BotPlatform.Telegram);
|
|
943
|
+
expect(telegram.warnings).toContain(BotInspectWarning.WebPort);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it("keeps quiet about the web address for a bot that reads neither PORT nor a webhook", () => {
|
|
947
|
+
const result = project({
|
|
948
|
+
...DISCORD_NODE,
|
|
949
|
+
"index.js": "const token = process.env.DISCORD_TOKEN;\nconst port = process.env.SERVER_PORT;\n",
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
expect(result.env.map((entry) => entry.key)).toEqual([
|
|
953
|
+
"DISCORD_TOKEN",
|
|
954
|
+
"SERVER_PORT",
|
|
955
|
+
]);
|
|
956
|
+
expect(result.warnings).not.toContain(BotInspectWarning.WebPort);
|
|
957
|
+
expect(project(TELEGRAM_PYTHON).warnings).not.toContain(BotInspectWarning.WebPort);
|
|
958
|
+
});
|
|
681
959
|
});
|
|
682
960
|
|
|
683
961
|
describe("detect with serverk.json", () => {
|
|
@@ -711,6 +989,32 @@ describe("detect with serverk.json", () => {
|
|
|
711
989
|
expect(result.warnings).toContain(BotInspectWarning.ManifestDisagrees);
|
|
712
990
|
});
|
|
713
991
|
|
|
992
|
+
it("says when the file names a runtime the code is not written in", () => {
|
|
993
|
+
const disagrees = project({
|
|
994
|
+
...TELEGRAM_PYTHON,
|
|
995
|
+
"serverk.json": JSON.stringify({
|
|
996
|
+
runtime: "node",
|
|
997
|
+
}),
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
expect(disagrees.runtime).toBe(BotRuntime.Node);
|
|
1001
|
+
expect(disagrees.warnings).toEqual([
|
|
1002
|
+
BotInspectWarning.NoStart,
|
|
1003
|
+
BotInspectWarning.NoManifest,
|
|
1004
|
+
BotInspectWarning.ManifestDisagrees,
|
|
1005
|
+
]);
|
|
1006
|
+
|
|
1007
|
+
const agrees = project({
|
|
1008
|
+
...TELEGRAM_PYTHON,
|
|
1009
|
+
"serverk.json": JSON.stringify({
|
|
1010
|
+
runtime: "python",
|
|
1011
|
+
}),
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
expect(agrees.runtime).toBe(BotRuntime.Python);
|
|
1015
|
+
expect(agrees.warnings).toEqual([]);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
714
1018
|
it("installs with the file's own install command over the lockfile's", () => {
|
|
715
1019
|
const result = project({
|
|
716
1020
|
...DISCORD_NODE,
|
|
@@ -825,6 +1129,9 @@ describe("detect env suggestions", () => {
|
|
|
825
1129
|
{
|
|
826
1130
|
file: ".env.dist",
|
|
827
1131
|
},
|
|
1132
|
+
{
|
|
1133
|
+
file: "sample.env",
|
|
1134
|
+
},
|
|
828
1135
|
];
|
|
829
1136
|
|
|
830
1137
|
for (const item of CASES) {
|
package/src/bot/detect/detect.ts
CHANGED
|
@@ -2,15 +2,16 @@ import type { BotFramework } from "./botFramework";
|
|
|
2
2
|
import { BOT_FRAMEWORKS, type BotFrameworkSpec, botFrameworkSpec } from "./botFrameworks";
|
|
3
3
|
import { BotInspectWarning } from "./botInspectWarning";
|
|
4
4
|
import { BotPackageManager } from "./botPackageManager";
|
|
5
|
-
import
|
|
5
|
+
import { BotPlatform } from "./botPlatform";
|
|
6
6
|
import { BOT_RUNTIME_PACKAGES, BotRuntime } from "./botRuntime";
|
|
7
7
|
import { BOT_TOKEN_KEYS, platformOfKey } from "./botToken";
|
|
8
|
+
import { nodeLtsMajorOf } from "./detectedVersion";
|
|
8
9
|
import type { DetectEntry, DetectedCommand, DetectedEnvKey, DetectInput, DetectResult } from "./detectResult";
|
|
9
10
|
import { DETECT_TABLE, type DetectVersionFile } from "./detectTable";
|
|
10
11
|
import { ENV_EXAMPLE_FILES, ENV_FILE, isSecretEnvFile, parseEnvExample } from "./envExample";
|
|
11
12
|
import { isEnvKey } from "./envKey";
|
|
12
13
|
import { isNodeSource, isPythonSource, scanEnvKeys } from "./envScan";
|
|
13
|
-
import { BOT_PROTECTED_KEYS, BOT_PROVIDED_KEYS, isReservedBotKey } from "./keys";
|
|
14
|
+
import { BOT_PROTECTED_KEYS, BOT_PROVIDED_KEYS, isReservedBotKey, PORT } from "./keys";
|
|
14
15
|
import { isSensitiveKey } from "./sensitiveKey";
|
|
15
16
|
import { parseServerkJson, SERVERK_JSON, type ServerkJson } from "./serverkJson";
|
|
16
17
|
|
|
@@ -79,6 +80,8 @@ const TOKEN_KEY = /TOKEN/i;
|
|
|
79
80
|
|
|
80
81
|
const BOT_KEY = /BOT/i;
|
|
81
82
|
|
|
83
|
+
const WEBHOOK_KEY = /WEBHOOK/i;
|
|
84
|
+
|
|
82
85
|
const NODE_IMPORT = /(?:\brequire\s*\(\s*|\bfrom\s+|\bimport\s*\(\s*|\bimport\s+)(["'])([^"'\n]+)\1/g;
|
|
83
86
|
|
|
84
87
|
const PYTHON_IMPORT = /^[ \t]*(?:from[ \t]+([A-Za-z_]\w*)|import[ \t]+([A-Za-z_]\w*))/gm;
|
|
@@ -192,14 +195,16 @@ const START_FILES: readonly string[] = [
|
|
|
192
195
|
];
|
|
193
196
|
|
|
194
197
|
const FIXED_FILES: readonly string[] = [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
...new Set([
|
|
199
|
+
DETECT_TABLE.node.manifest,
|
|
200
|
+
REQUIREMENTS_FILE,
|
|
201
|
+
PYPROJECT_FILE,
|
|
202
|
+
DETECT_TABLE.procfile.file,
|
|
203
|
+
SERVERK_JSON,
|
|
204
|
+
...ENV_EXAMPLE_FILES,
|
|
205
|
+
...DETECT_TABLE.node.versionFiles.map((entry) => entry.file),
|
|
206
|
+
...DETECT_TABLE.python.versionFiles.map((entry) => entry.file),
|
|
207
|
+
]),
|
|
203
208
|
];
|
|
204
209
|
|
|
205
210
|
const sourceRank = (path: string) => {
|
|
@@ -587,7 +592,11 @@ const nodeBuildOf = (packageJson: PackageJson | null, manager: string): Detected
|
|
|
587
592
|
};
|
|
588
593
|
};
|
|
589
594
|
|
|
590
|
-
const versionFromFile = (
|
|
595
|
+
const versionFromFile = (
|
|
596
|
+
text: string,
|
|
597
|
+
spec: DetectVersionFile,
|
|
598
|
+
aliasOf: (value: string) => string | null,
|
|
599
|
+
): string | null => {
|
|
591
600
|
for (const raw of text.split("\n")) {
|
|
592
601
|
let line = raw.trim();
|
|
593
602
|
|
|
@@ -623,7 +632,7 @@ const versionFromFile = (text: string, spec: DetectVersionFile): string | null =
|
|
|
623
632
|
line = line.slice(spec.prefix.length);
|
|
624
633
|
}
|
|
625
634
|
|
|
626
|
-
return VERSION_NUMBER.exec(line)?.[1] ??
|
|
635
|
+
return VERSION_NUMBER.exec(line)?.[1] ?? aliasOf(line);
|
|
627
636
|
}
|
|
628
637
|
|
|
629
638
|
return null;
|
|
@@ -633,6 +642,7 @@ const versionFromFiles = (
|
|
|
633
642
|
specs: readonly DetectVersionFile[],
|
|
634
643
|
read: (path: string) => string | null,
|
|
635
644
|
parts: number,
|
|
645
|
+
aliasOf: (value: string) => string | null = () => null,
|
|
636
646
|
): string | null => {
|
|
637
647
|
for (const spec of specs) {
|
|
638
648
|
const text = read(spec.file);
|
|
@@ -641,7 +651,7 @@ const versionFromFiles = (
|
|
|
641
651
|
continue;
|
|
642
652
|
}
|
|
643
653
|
|
|
644
|
-
const version = versionFromFile(text, spec);
|
|
654
|
+
const version = versionFromFile(text, spec, aliasOf);
|
|
645
655
|
|
|
646
656
|
if (version !== null) {
|
|
647
657
|
return version.split(".").slice(0, parts).join(".");
|
|
@@ -731,7 +741,7 @@ const nodeRequirementOf = (
|
|
|
731
741
|
}
|
|
732
742
|
}
|
|
733
743
|
|
|
734
|
-
const version = versionFromFiles(DETECT_TABLE.node.versionFiles, read, 1);
|
|
744
|
+
const version = versionFromFiles(DETECT_TABLE.node.versionFiles, read, 1, nodeLtsMajorOf);
|
|
735
745
|
|
|
736
746
|
return version === null
|
|
737
747
|
? null
|
|
@@ -845,6 +855,19 @@ const committedWarnings = (paths: ReadonlySet<string>, warnings: Set<BotInspectW
|
|
|
845
855
|
}
|
|
846
856
|
};
|
|
847
857
|
|
|
858
|
+
const webPortWarning = (
|
|
859
|
+
platform: BotPlatform | null,
|
|
860
|
+
env: readonly DetectedEnvKey[],
|
|
861
|
+
warnings: Set<BotInspectWarning>,
|
|
862
|
+
) => {
|
|
863
|
+
const keys = env.map((entry) => entry.key);
|
|
864
|
+
const webhook = platform !== BotPlatform.Discord && keys.some((key) => WEBHOOK_KEY.test(key));
|
|
865
|
+
|
|
866
|
+
if (keys.includes(PORT) || webhook) {
|
|
867
|
+
warnings.add(BotInspectWarning.WebPort);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
848
871
|
interface EnvCollector {
|
|
849
872
|
add(key: string, source: string, sensitive: boolean | null, example: string | null): void;
|
|
850
873
|
entries: DetectedEnvKey[];
|
|
@@ -1165,6 +1188,8 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
1165
1188
|
);
|
|
1166
1189
|
const platform = platformOf(serverk, framework, candidates, keyPlatforms, warnings);
|
|
1167
1190
|
|
|
1191
|
+
webPortWarning(platform, env.entries, warnings);
|
|
1192
|
+
|
|
1168
1193
|
return {
|
|
1169
1194
|
runtime,
|
|
1170
1195
|
package: runtime ? BOT_RUNTIME_PACKAGES[runtime] : null,
|