@serverkgg/bridge 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/bot/botCauses.test.ts +292 -0
- package/src/bot/botCauses.ts +58 -0
- package/src/bot/botDeploy.test.ts +260 -1
- package/src/bot/botDeploy.ts +125 -6
- package/src/bot/botState.ts +6 -0
- package/src/bot/detect/botFrameworks.ts +1 -0
- package/src/bot/detect/botInspectWarning.ts +4 -1
- package/src/bot/detect/botPackageManager.ts +3 -0
- package/src/bot/detect/corpus/alexmubarakshin-ts-telegraf-boilerplate.json +37 -0
- package/src/bot/detect/corpus/alsarmad-discord-quran-bot.json +40 -0
- package/src/bot/detect/corpus/anasty17-mirror-leech-telegram-bot.json +50 -0
- package/src/bot/detect/corpus/anidiotsguide-guidebot.json +36 -0
- package/src/bot/detect/corpus/anonymousx1025-anonxmusic.json +45 -0
- package/src/bot/detect/corpus/bot-base-telegram-bot-template.json +35 -0
- package/src/bot/detect/corpus/bracketed-templates-discordjs.json +40 -0
- package/src/bot/detect/corpus/cog-creators-red-discordbot.json +49 -0
- package/src/bot/detect/corpus/cranberriez-discord.ts-bot-template.json +35 -0
- package/src/bot/detect/corpus/crazycat256-nextcord-bot-template.json +38 -0
- package/src/bot/detect/corpus/discord-tickets-bot.json +45 -0
- package/src/bot/detect/corpus/edjopato-telegram-typescript-bot-template.json +37 -0
- package/src/bot/detect/corpus/haniali3161-bot-adhkar-telegram.json +33 -0
- package/src/bot/detect/corpus/hellosverre-discord-ts-template.json +34 -0
- package/src/bot/detect/corpus/kevinnovak-discord-bot-typescript-template.json +43 -0
- package/src/bot/detect/corpus/kkrypt0nn-python-discord-bot-template.json +41 -0
- package/src/bot/detect/corpus/kolezka-telegram-grammy-template.json +37 -0
- package/src/bot/detect/corpus/latand-tgbot_template_v3.json +37 -0
- package/src/bot/detect/corpus/mahmoudbm77-zekr-telegram-bot.json +36 -0
- package/src/bot/detect/corpus/mericcan41-discordjs-v14-template-ts.json +35 -0
- package/src/bot/detect/corpus/modmail-dev-modmail.json +52 -0
- package/src/bot/detect/corpus/nezdemkovski-telegram-bot-boilerplate.json +39 -0
- package/src/bot/detect/corpus/notchum-discord-bot-template.json +39 -0
- package/src/bot/detect/corpus/python-discord-bot.json +44 -0
- package/src/bot/detect/corpus/python-telegram-bot-rules-bot.json +39 -0
- package/src/bot/detect/corpus/rajdave69-pycord-bot-template.json +31 -0
- package/src/bot/detect/corpus/rapptz-robodanny.json +36 -0
- package/src/bot/detect/corpus/ry2x-typescript-discord.js-v.14-template.json +35 -0
- package/src/bot/detect/corpus/saiteja-madha-discord-js-bot.json +45 -0
- package/src/bot/detect/corpus/seegreatness-discord.js-for-heroku.json +37 -0
- package/src/bot/detect/corpus/serverk-discord-node.json +36 -0
- package/src/bot/detect/corpus/serverk-discord-python.json +35 -0
- package/src/bot/detect/corpus/serverk-telegram-node.json +36 -0
- package/src/bot/detect/corpus/serverk-telegram-python.json +35 -0
- package/src/bot/detect/corpus/shuruhatik-quran-radio-bot-discord.json +33 -0
- package/src/bot/detect/corpus/skyra-project-skyra.json +46 -0
- package/src/bot/detect/corpus/tgbot-collection-ytdlbot.json +41 -0
- package/src/bot/detect/corpus/zeriodev-music-bot.json +38 -0
- package/src/bot/detect/detect.corpus.test.ts +77 -0
- package/src/bot/detect/detect.test.ts +443 -17
- package/src/bot/detect/detect.ts +375 -34
- package/src/bot/detect/detectResult.ts +4 -0
- package/src/bot/detect/detectTable.test.ts +22 -6
- package/src/bot/detect/detectTable.ts +187 -19
- package/src/bot/detect/detectedVersion.ts +5 -0
- package/src/bot/detect/envExample.test.ts +55 -1
- package/src/bot/detect/envExample.ts +14 -0
- package/src/bot/detect/index.ts +1 -0
- package/src/bot/detect/keys.test.ts +32 -1
- package/src/bot/detect/keys.ts +15 -0
- package/src/bot/detect/serverkJson.test.ts +2 -0
- package/src/bot/detect/serverkJson.ts +4 -1
- package/src/bot/index.ts +1 -0
- package/src/manifest/manifest.ts +2 -1
- package/src/modules/bridge.ts +2 -0
- package/src/modules/bridgeModule.ts +5 -0
- package/src/probes/minecraftPing.test.ts +2 -1
- package/src/protocol/bridgeCrash.ts +49 -0
- package/src/protocol/bridgeEvents.ts +9 -0
- package/src/rcon/rconClient.test.ts +3 -2
- package/src/runtime/bridgeDriver.test.ts +45 -0
- package/src/runtime/bridgeDriver.ts +25 -0
- package/src/runtime/bridgeEvents.ts +2 -0
- package/src/runtime/bridgeSupervisor.test.ts +163 -3
- package/src/runtime/bridgeSupervisor.ts +42 -1
- package/src/runtime/serve.ts +26 -5
package/src/bot/detect/detect.ts
CHANGED
|
@@ -6,8 +6,8 @@ import type { BotPlatform } from "./botPlatform";
|
|
|
6
6
|
import { BOT_RUNTIME_PACKAGES, BotRuntime } from "./botRuntime";
|
|
7
7
|
import { BOT_TOKEN_KEYS, platformOfKey } from "./botToken";
|
|
8
8
|
import type { DetectEntry, DetectedCommand, DetectedEnvKey, DetectInput, DetectResult } from "./detectResult";
|
|
9
|
-
import { DETECT_TABLE } from "./detectTable";
|
|
10
|
-
import { ENV_EXAMPLE_FILES, parseEnvExample } from "./envExample";
|
|
9
|
+
import { DETECT_TABLE, type DetectVersionFile } from "./detectTable";
|
|
10
|
+
import { ENV_EXAMPLE_FILES, ENV_FILE, isSecretEnvFile, parseEnvExample } from "./envExample";
|
|
11
11
|
import { isEnvKey } from "./envKey";
|
|
12
12
|
import { isNodeSource, isPythonSource, scanEnvKeys } from "./envScan";
|
|
13
13
|
import { BOT_PROTECTED_KEYS, BOT_PROVIDED_KEYS, isReservedBotKey } from "./keys";
|
|
@@ -41,15 +41,39 @@ const VENV_SEGMENTS: ReadonlySet<string> = new Set([
|
|
|
41
41
|
"venv",
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
|
-
const
|
|
44
|
+
const DATABASE_FILE = /\.(?:sqlite3?|db)$/i;
|
|
45
45
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
46
|
+
const PYTHON_PACKAGE_MAIN = /^([A-Za-z_]\w*)\/__main__\.py$/;
|
|
47
|
+
|
|
48
|
+
const NOT_A_BOT_PACKAGE: ReadonlySet<string> = new Set([
|
|
49
|
+
"tests",
|
|
50
|
+
"test",
|
|
51
|
+
"docs",
|
|
52
|
+
"examples",
|
|
53
|
+
"scripts",
|
|
50
54
|
]);
|
|
51
55
|
|
|
52
|
-
const
|
|
56
|
+
const WINDOWS_COMMANDS: ReadonlySet<string> = new Set([
|
|
57
|
+
"cls",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const COMMAND_SEPARATOR = /&&|\|\||;|\|/;
|
|
61
|
+
|
|
62
|
+
const MANAGER_FIELD = /^(npm|pnpm|yarn|bun)@(\d+)/;
|
|
63
|
+
|
|
64
|
+
const ENGINE_MANAGERS: readonly string[] = [
|
|
65
|
+
"pnpm",
|
|
66
|
+
"bun",
|
|
67
|
+
"yarn",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const YARN_BERRY = "yarn-berry";
|
|
71
|
+
|
|
72
|
+
const VERSION_NUMBER = /^v?(\d+(?:\.\d+)*)/;
|
|
73
|
+
|
|
74
|
+
const RANGE_COMPARATOR = /^(>=|<=|>|<|=|\^|~)?v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/;
|
|
75
|
+
|
|
76
|
+
const HYPHEN_RANGE = /(\S+)\s+-\s+(\S+)/g;
|
|
53
77
|
|
|
54
78
|
const TOKEN_KEY = /TOKEN/i;
|
|
55
79
|
|
|
@@ -71,6 +95,14 @@ const MANAGERS: Readonly<Record<string, BotPackageManager>> = {
|
|
|
71
95
|
npm: BotPackageManager.Npm,
|
|
72
96
|
pnpm: BotPackageManager.Pnpm,
|
|
73
97
|
yarn: BotPackageManager.Yarn,
|
|
98
|
+
[YARN_BERRY]: BotPackageManager.Yarn,
|
|
99
|
+
bun: BotPackageManager.Bun,
|
|
100
|
+
requirements: BotPackageManager.Pip,
|
|
101
|
+
uv: BotPackageManager.Uv,
|
|
102
|
+
poetry: BotPackageManager.Pip,
|
|
103
|
+
pdm: BotPackageManager.Pip,
|
|
104
|
+
pipenv: BotPackageManager.Pipenv,
|
|
105
|
+
project: BotPackageManager.Pip,
|
|
74
106
|
};
|
|
75
107
|
|
|
76
108
|
export interface PythonRequirement {
|
|
@@ -166,6 +198,8 @@ const FIXED_FILES: readonly string[] = [
|
|
|
166
198
|
DETECT_TABLE.procfile.file,
|
|
167
199
|
SERVERK_JSON,
|
|
168
200
|
...ENV_EXAMPLE_FILES,
|
|
201
|
+
...DETECT_TABLE.node.versionFiles.map((entry) => entry.file),
|
|
202
|
+
...DETECT_TABLE.python.versionFiles.map((entry) => entry.file),
|
|
169
203
|
];
|
|
170
204
|
|
|
171
205
|
const sourceRank = (path: string) => {
|
|
@@ -366,6 +400,18 @@ const frameworksOf = (
|
|
|
366
400
|
return declared.length > 0 ? declared : specs.filter((spec) => spec.modules.some((name) => imports.has(name)));
|
|
367
401
|
};
|
|
368
402
|
|
|
403
|
+
const runsWindowsCommand = (command: string | null) => {
|
|
404
|
+
if (command === null) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return command.split(COMMAND_SEPARATOR).some((segment) => {
|
|
409
|
+
const program = segment.trim().split(/\s+/)[0] ?? "";
|
|
410
|
+
|
|
411
|
+
return WINDOWS_COMMANDS.has(program.toLowerCase());
|
|
412
|
+
});
|
|
413
|
+
};
|
|
414
|
+
|
|
369
415
|
const procfileCommand = (text: string | null): DetectedCommand | null => {
|
|
370
416
|
if (text === null) {
|
|
371
417
|
return null;
|
|
@@ -425,13 +471,29 @@ const nodeStartOf = (packageJson: PackageJson | null, has: (path: string) => boo
|
|
|
425
471
|
: null;
|
|
426
472
|
};
|
|
427
473
|
|
|
428
|
-
const pythonStartOf = (has: (path: string) => boolean): DetectedCommand | null => {
|
|
474
|
+
const pythonStartOf = (has: (path: string) => boolean, paths: ReadonlySet<string>): DetectedCommand | null => {
|
|
429
475
|
const file = DETECT_TABLE.python.startFiles.find(has);
|
|
430
476
|
|
|
431
|
-
|
|
477
|
+
if (file) {
|
|
478
|
+
return {
|
|
479
|
+
command: `python ${file}`,
|
|
480
|
+
source: file,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const packages = [
|
|
485
|
+
...paths,
|
|
486
|
+
]
|
|
487
|
+
.map((path) => PYTHON_PACKAGE_MAIN.exec(path)?.[1])
|
|
488
|
+
.filter((name): name is string => name !== undefined && !skipped(name) && !NOT_A_BOT_PACKAGE.has(name))
|
|
489
|
+
.sort(compareCodePoints);
|
|
490
|
+
|
|
491
|
+
const name = packages.length === 1 ? packages[0] : null;
|
|
492
|
+
|
|
493
|
+
return name
|
|
432
494
|
? {
|
|
433
|
-
command: `python ${
|
|
434
|
-
source:
|
|
495
|
+
command: `python -m ${name}`,
|
|
496
|
+
source: `${name}/__main__.py`,
|
|
435
497
|
}
|
|
436
498
|
: null;
|
|
437
499
|
};
|
|
@@ -442,6 +504,243 @@ const nodeStartFileOf = (packageJson: PackageJson | null, has: (path: string) =>
|
|
|
442
504
|
return (main !== null && has(main)) || DETECT_TABLE.node.startFiles.some(has);
|
|
443
505
|
};
|
|
444
506
|
|
|
507
|
+
interface NodeManager {
|
|
508
|
+
key: string;
|
|
509
|
+
locked: boolean;
|
|
510
|
+
source: string;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const lockfileOf = (manager: string, has: (path: string) => boolean) => {
|
|
514
|
+
const lockManager = manager === YARN_BERRY ? "yarn" : manager;
|
|
515
|
+
|
|
516
|
+
return DETECT_TABLE.node.lockfiles.find((entry) => entry.manager === lockManager && has(entry.file)) ?? null;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const nodeManagerOf = (packageJson: PackageJson | null, has: (path: string) => boolean): NodeManager => {
|
|
520
|
+
const declared = packageJson?.[DETECT_TABLE.node.managerField];
|
|
521
|
+
const field = typeof declared === "string" ? MANAGER_FIELD.exec(declared.trim()) : null;
|
|
522
|
+
|
|
523
|
+
if (field) {
|
|
524
|
+
const key = field[1] === "yarn" && Number(field[2]) >= 2 ? YARN_BERRY : (field[1] ?? "npm");
|
|
525
|
+
const lock = lockfileOf(key, has);
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
key,
|
|
529
|
+
locked: lock !== null,
|
|
530
|
+
source: lock?.file ?? DETECT_TABLE.node.manifest,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
for (const entry of DETECT_TABLE.node.lockfiles) {
|
|
535
|
+
if (entry.manager !== "npm" && has(entry.file)) {
|
|
536
|
+
return {
|
|
537
|
+
key: entry.manager === "yarn" && has(DETECT_TABLE.node.berryFile) ? YARN_BERRY : entry.manager,
|
|
538
|
+
locked: true,
|
|
539
|
+
source: entry.file,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (has(DETECT_TABLE.node.berryFile)) {
|
|
545
|
+
return {
|
|
546
|
+
key: YARN_BERRY,
|
|
547
|
+
locked: false,
|
|
548
|
+
source: DETECT_TABLE.node.berryFile,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const engines = recordOf(packageJson?.engines);
|
|
553
|
+
const engine = ENGINE_MANAGERS.find((manager) => typeof engines[manager] === "string") ?? null;
|
|
554
|
+
|
|
555
|
+
if (engine) {
|
|
556
|
+
return {
|
|
557
|
+
key: engine,
|
|
558
|
+
locked: false,
|
|
559
|
+
source: DETECT_TABLE.node.manifest,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const npmLock = lockfileOf("npm", has);
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
key: "npm",
|
|
567
|
+
locked: npmLock !== null,
|
|
568
|
+
source: npmLock?.file ?? DETECT_TABLE.node.manifest,
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const nodeBuildOf = (packageJson: PackageJson | null, manager: string): DetectedCommand | null => {
|
|
573
|
+
const script = recordOf(packageJson?.scripts)[DETECT_TABLE.node.buildScript];
|
|
574
|
+
|
|
575
|
+
if (typeof script !== "string" || script.trim().length === 0) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const run = DETECT_TABLE.node.runCommands[manager] ?? DETECT_TABLE.node.runCommands.npm ?? [];
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
command: [
|
|
583
|
+
...run,
|
|
584
|
+
DETECT_TABLE.node.buildScript,
|
|
585
|
+
].join(" "),
|
|
586
|
+
source: DETECT_TABLE.node.manifest,
|
|
587
|
+
};
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const versionFromFile = (text: string, spec: DetectVersionFile): string | null => {
|
|
591
|
+
for (const raw of text.split("\n")) {
|
|
592
|
+
let line = raw.trim();
|
|
593
|
+
|
|
594
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (spec.tool !== undefined) {
|
|
599
|
+
const [tool, ...rest] = line.split(/\s+/);
|
|
600
|
+
|
|
601
|
+
if (tool !== spec.tool) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
line = rest[0] ?? "";
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (spec.key !== undefined) {
|
|
609
|
+
const match = new RegExp(`^${spec.key}\\s*=\\s*"?([^"\\s]+)`).exec(line);
|
|
610
|
+
|
|
611
|
+
if (!match) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
line = match[1] ?? "";
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (spec.prefix !== undefined) {
|
|
619
|
+
if (!line.startsWith(spec.prefix)) {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
line = line.slice(spec.prefix.length);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return VERSION_NUMBER.exec(line)?.[1] ?? null;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return null;
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
const versionFromFiles = (
|
|
633
|
+
specs: readonly DetectVersionFile[],
|
|
634
|
+
read: (path: string) => string | null,
|
|
635
|
+
parts: number,
|
|
636
|
+
): string | null => {
|
|
637
|
+
for (const spec of specs) {
|
|
638
|
+
const text = read(spec.file);
|
|
639
|
+
|
|
640
|
+
if (text === null) {
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const version = versionFromFile(text, spec);
|
|
645
|
+
|
|
646
|
+
if (version !== null) {
|
|
647
|
+
return version.split(".").slice(0, parts).join(".");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return null;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const comparatorAdmits = (comparator: string, major: number): boolean | null => {
|
|
655
|
+
const match = RANGE_COMPARATOR.exec(comparator);
|
|
656
|
+
|
|
657
|
+
if (!match) {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const base = Number(match[2]);
|
|
662
|
+
const minor = match[3];
|
|
663
|
+
|
|
664
|
+
switch (match[1] ?? "") {
|
|
665
|
+
case ">=":
|
|
666
|
+
return major >= base;
|
|
667
|
+
case ">":
|
|
668
|
+
return minor === undefined ? major > base : major >= base;
|
|
669
|
+
case "<":
|
|
670
|
+
return major < base || (major === base && minor !== undefined && minor !== "0");
|
|
671
|
+
case "<=":
|
|
672
|
+
return major <= base;
|
|
673
|
+
default:
|
|
674
|
+
return major === base;
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const comparatorsOf = (clause: string) => {
|
|
679
|
+
return clause
|
|
680
|
+
.replace(HYPHEN_RANGE, ">=$1 <=$2")
|
|
681
|
+
.trim()
|
|
682
|
+
.split(/\s+/)
|
|
683
|
+
.filter((token) => token.length > 0);
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const rangeAdmits = (range: string, major: number) => {
|
|
687
|
+
return range.split("||").some((clause) => {
|
|
688
|
+
const verdicts = comparatorsOf(clause).map((token) => comparatorAdmits(token, major));
|
|
689
|
+
|
|
690
|
+
return verdicts.length === 0 || verdicts.some((verdict) => verdict === null) || verdicts.every(Boolean);
|
|
691
|
+
});
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const requestedMajorOf = (range: string): string | null => {
|
|
695
|
+
const comparators = range
|
|
696
|
+
.split("||")
|
|
697
|
+
.flatMap(comparatorsOf)
|
|
698
|
+
.map((token) => RANGE_COMPARATOR.exec(token))
|
|
699
|
+
.filter((match) => match !== null);
|
|
700
|
+
const lower = comparators.filter((match) => match[1] !== "<" && match[1] !== "<=").map((match) => Number(match[2]));
|
|
701
|
+
|
|
702
|
+
if (lower.length > 0) {
|
|
703
|
+
return String(Math.min(...lower));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const upper = comparators.map((match) => (match[1] === "<" ? Number(match[2]) - 1 : Number(match[2])));
|
|
707
|
+
|
|
708
|
+
return upper.length === 0 ? null : String(Math.max(...upper));
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
interface NodeRequirement {
|
|
712
|
+
version: string;
|
|
713
|
+
unmet: boolean;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const nodeRequirementOf = (
|
|
717
|
+
packageJson: PackageJson | null,
|
|
718
|
+
read: (path: string) => string | null,
|
|
719
|
+
): NodeRequirement | null => {
|
|
720
|
+
const image = Number(DETECT_TABLE.node.defaultVersion);
|
|
721
|
+
const engine = recordOf(packageJson?.engines)[DETECT_TABLE.node.enginesKey];
|
|
722
|
+
|
|
723
|
+
if (typeof engine === "string" && engine.trim().length > 0) {
|
|
724
|
+
const version = requestedMajorOf(engine);
|
|
725
|
+
|
|
726
|
+
if (version !== null) {
|
|
727
|
+
return {
|
|
728
|
+
version,
|
|
729
|
+
unmet: !rangeAdmits(engine, image),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const version = versionFromFiles(DETECT_TABLE.node.versionFiles, read, 1);
|
|
735
|
+
|
|
736
|
+
return version === null
|
|
737
|
+
? null
|
|
738
|
+
: {
|
|
739
|
+
version,
|
|
740
|
+
unmet: Number(version) !== image,
|
|
741
|
+
};
|
|
742
|
+
};
|
|
743
|
+
|
|
445
744
|
const decideRuntime = (
|
|
446
745
|
node: {
|
|
447
746
|
manifest: boolean;
|
|
@@ -536,9 +835,13 @@ const committedWarnings = (paths: ReadonlySet<string>, warnings: Set<BotInspectW
|
|
|
536
835
|
|
|
537
836
|
const name = segments.at(-1) ?? "";
|
|
538
837
|
|
|
539
|
-
if (!skipped(path) &&
|
|
838
|
+
if (!skipped(path) && isSecretEnvFile(name)) {
|
|
540
839
|
warnings.add(BotInspectWarning.EnvCommitted);
|
|
541
840
|
}
|
|
841
|
+
|
|
842
|
+
if (!skipped(path) && DATABASE_FILE.test(name)) {
|
|
843
|
+
warnings.add(BotInspectWarning.DatabaseCommitted);
|
|
844
|
+
}
|
|
542
845
|
}
|
|
543
846
|
};
|
|
544
847
|
|
|
@@ -657,7 +960,6 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
657
960
|
const packageText = read(DETECT_TABLE.node.manifest);
|
|
658
961
|
const packageJson = packageText === null ? null : parseObject(packageText);
|
|
659
962
|
const hasPackage = has(DETECT_TABLE.node.manifest);
|
|
660
|
-
const nodeLock = DETECT_TABLE.node.lockfiles.find((entry) => has(entry.file)) ?? null;
|
|
661
963
|
const pythonManifest = DETECT_TABLE.python.manifests.find((entry) => has(entry.file)) ?? null;
|
|
662
964
|
const requirementsText = read(REQUIREMENTS_FILE);
|
|
663
965
|
const pyprojectText = read(PYPROJECT_FILE);
|
|
@@ -716,37 +1018,54 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
716
1018
|
|
|
717
1019
|
let packageManager: BotPackageManager | null = null;
|
|
718
1020
|
let install: DetectedCommand | null = null;
|
|
1021
|
+
let build: DetectedCommand | null = null;
|
|
1022
|
+
let nodeVersion: string | null = null;
|
|
1023
|
+
let pythonVersion: string | null = null;
|
|
719
1024
|
|
|
720
1025
|
if (runtime === BotRuntime.Node) {
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
packageManager = BotPackageManager.Npm;
|
|
1026
|
+
if (hasPackage) {
|
|
1027
|
+
const manager = nodeManagerOf(packageJson, has);
|
|
1028
|
+
const commands = manager.locked
|
|
1029
|
+
? DETECT_TABLE.node.installCommands.locked
|
|
1030
|
+
: DETECT_TABLE.node.installCommands.unlocked;
|
|
1031
|
+
|
|
1032
|
+
packageManager = MANAGERS[manager.key] ?? null;
|
|
729
1033
|
install = {
|
|
730
|
-
command: (
|
|
731
|
-
source:
|
|
1034
|
+
command: (commands[manager.key] ?? []).join(" "),
|
|
1035
|
+
source: manager.source,
|
|
732
1036
|
};
|
|
733
|
-
|
|
1037
|
+
build = nodeBuildOf(packageJson, manager.key);
|
|
1038
|
+
|
|
1039
|
+
if (!manager.locked) {
|
|
1040
|
+
warnings.add(BotInspectWarning.NoLockfile);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const requirement = nodeRequirementOf(packageJson, read);
|
|
1044
|
+
|
|
1045
|
+
nodeVersion = requirement?.version ?? null;
|
|
1046
|
+
|
|
1047
|
+
if (requirement?.unmet) {
|
|
1048
|
+
warnings.add(BotInspectWarning.NodeVersionUnmet);
|
|
1049
|
+
}
|
|
734
1050
|
} else {
|
|
735
1051
|
warnings.add(BotInspectWarning.NoManifest);
|
|
736
1052
|
}
|
|
737
1053
|
}
|
|
738
1054
|
|
|
739
1055
|
if (runtime === BotRuntime.Python) {
|
|
1056
|
+
pythonVersion = versionFromFiles(DETECT_TABLE.python.versionFiles, read, 2);
|
|
1057
|
+
|
|
740
1058
|
if (pythonManifest) {
|
|
741
|
-
packageManager =
|
|
1059
|
+
packageManager = MANAGERS[pythonManifest.manager] ?? null;
|
|
742
1060
|
install = {
|
|
743
|
-
command: [
|
|
744
|
-
"pip",
|
|
745
|
-
...(DETECT_TABLE.python.installArguments[pythonManifest.manager] ?? []),
|
|
746
|
-
].join(" "),
|
|
1061
|
+
command: (DETECT_TABLE.python.installCommands[pythonManifest.manager] ?? []).join(" "),
|
|
747
1062
|
source: pythonManifest.file,
|
|
748
1063
|
};
|
|
749
1064
|
|
|
1065
|
+
if (DETECT_TABLE.python.unhonouredManagers.includes(pythonManifest.manager)) {
|
|
1066
|
+
warnings.add(BotInspectWarning.LockfileNotHonoured);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
750
1069
|
const declared = pythonManifest.file === REQUIREMENTS_FILE ? requirements : pyproject;
|
|
751
1070
|
|
|
752
1071
|
if (declared.some((requirement) => !requirement.pinned)) {
|
|
@@ -768,26 +1087,41 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
768
1087
|
};
|
|
769
1088
|
}
|
|
770
1089
|
|
|
1090
|
+
if (serverk?.build) {
|
|
1091
|
+
build = {
|
|
1092
|
+
command: serverk.build,
|
|
1093
|
+
source: SERVERK_JSON,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
771
1097
|
const procfile = procfileCommand(read(DETECT_TABLE.procfile.file));
|
|
772
1098
|
const detectedStart =
|
|
773
1099
|
runtime === BotRuntime.Node
|
|
774
1100
|
? nodeStartOf(packageJson, has)
|
|
775
1101
|
: runtime === BotRuntime.Python
|
|
776
|
-
? pythonStartOf(has)
|
|
1102
|
+
? pythonStartOf(has, paths)
|
|
777
1103
|
: null;
|
|
778
1104
|
const start: DetectedCommand | null = serverk?.start
|
|
779
1105
|
? {
|
|
780
1106
|
command: serverk.start,
|
|
781
1107
|
source: SERVERK_JSON,
|
|
782
1108
|
}
|
|
783
|
-
: (
|
|
1109
|
+
: (procfile ?? detectedStart);
|
|
784
1110
|
|
|
785
1111
|
if (!start) {
|
|
786
1112
|
warnings.add(BotInspectWarning.NoStart);
|
|
787
1113
|
}
|
|
788
1114
|
|
|
789
|
-
|
|
790
|
-
|
|
1115
|
+
const buildScript = recordOf(packageJson?.scripts)[DETECT_TABLE.node.buildScript];
|
|
1116
|
+
|
|
1117
|
+
const commands = [
|
|
1118
|
+
start?.command ?? null,
|
|
1119
|
+
build?.command ?? null,
|
|
1120
|
+
build?.source === DETECT_TABLE.node.manifest && typeof buildScript === "string" ? buildScript : null,
|
|
1121
|
+
];
|
|
1122
|
+
|
|
1123
|
+
if (commands.some(runsWindowsCommand)) {
|
|
1124
|
+
warnings.add(BotInspectWarning.WindowsCommand);
|
|
791
1125
|
}
|
|
792
1126
|
|
|
793
1127
|
committedWarnings(paths, warnings);
|
|
@@ -806,6 +1140,10 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
806
1140
|
}
|
|
807
1141
|
}
|
|
808
1142
|
|
|
1143
|
+
for (const entry of input.skippedEnv === undefined ? [] : parseEnvExample(input.skippedEnv)) {
|
|
1144
|
+
env.add(entry.key, ENV_FILE, null, null);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
809
1147
|
for (const [path, text] of sources) {
|
|
810
1148
|
for (const key of scanEnvKeys(path, text)) {
|
|
811
1149
|
env.add(key, path, null, null);
|
|
@@ -834,7 +1172,10 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
834
1172
|
framework,
|
|
835
1173
|
platform,
|
|
836
1174
|
install,
|
|
1175
|
+
build,
|
|
837
1176
|
start,
|
|
1177
|
+
nodeVersion,
|
|
1178
|
+
pythonVersion,
|
|
838
1179
|
env: env.entries,
|
|
839
1180
|
tokenKey: tokenKeyOf(platform, env.entries),
|
|
840
1181
|
warnings: warningsInOrder(warnings),
|
|
@@ -12,6 +12,7 @@ export interface DetectEntry {
|
|
|
12
12
|
export interface DetectInput {
|
|
13
13
|
files: readonly DetectEntry[];
|
|
14
14
|
contents: Readonly<Record<string, string>>;
|
|
15
|
+
skippedEnv?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface DetectedCommand {
|
|
@@ -33,7 +34,10 @@ export interface DetectResult {
|
|
|
33
34
|
framework: BotFramework | null;
|
|
34
35
|
platform: BotPlatform | null;
|
|
35
36
|
install: DetectedCommand | null;
|
|
37
|
+
build: DetectedCommand | null;
|
|
36
38
|
start: DetectedCommand | null;
|
|
39
|
+
nodeVersion: string | null;
|
|
40
|
+
pythonVersion: string | null;
|
|
37
41
|
env: DetectedEnvKey[];
|
|
38
42
|
tokenKey: string | null;
|
|
39
43
|
warnings: BotInspectWarning[];
|
|
@@ -9,20 +9,36 @@ describe("renderDetectTable", () => {
|
|
|
9
9
|
const rendered = renderDetectTable();
|
|
10
10
|
|
|
11
11
|
expect(rendered.endsWith("}\n")).toBe(true);
|
|
12
|
-
expect(rendered).toContain('\t"version":
|
|
12
|
+
expect(rendered).toContain('\t"version": 2,');
|
|
13
13
|
expect(JSON.parse(rendered)).toEqual(DETECT_TABLE);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it("names
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
it("names a locked and an unlocked install and a run command for every Node manager", () => {
|
|
17
|
+
const managers = new Set([
|
|
18
|
+
...DETECT_TABLE.node.lockfiles.map((lockfile) => lockfile.manager),
|
|
19
|
+
"yarn-berry",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
for (const manager of managers) {
|
|
23
|
+
expect(DETECT_TABLE.node.installCommands.locked[manager]).toBeDefined();
|
|
24
|
+
expect(DETECT_TABLE.node.installCommands.unlocked[manager]).toBeDefined();
|
|
25
|
+
expect(DETECT_TABLE.node.runCommands[manager]).toBeDefined();
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
expect(DETECT_TABLE.node.
|
|
28
|
+
expect(Number(DETECT_TABLE.node.defaultVersion)).toBeGreaterThan(0);
|
|
29
|
+
});
|
|
22
30
|
|
|
31
|
+
it("names an install command for every Python manifest and lockfile, and the ones it does not honour", () => {
|
|
23
32
|
for (const manifest of DETECT_TABLE.python.manifests) {
|
|
24
|
-
expect(DETECT_TABLE.python.
|
|
33
|
+
expect(DETECT_TABLE.python.installCommands[manifest.manager]).toBeDefined();
|
|
25
34
|
}
|
|
35
|
+
|
|
36
|
+
for (const manager of DETECT_TABLE.python.unhonouredManagers) {
|
|
37
|
+
expect(DETECT_TABLE.python.manifests.some((manifest) => manifest.manager === manager)).toBe(true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
expect(DETECT_TABLE.python.manifests[0]?.file).toBe("requirements.txt");
|
|
41
|
+
expect(DETECT_TABLE.python.defaultVersion).toMatch(/^\d+\.\d+$/);
|
|
26
42
|
});
|
|
27
43
|
|
|
28
44
|
it("carries the serverk.json rules the launchers need to agree with the parser", () => {
|