@serverkgg/bridge 0.1.4 → 0.1.5
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 +284 -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/botInspectWarning.ts +3 -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 +33 -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 +414 -17
- package/src/bot/detect/detect.ts +347 -36
- 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/protocol/bridgeCrash.ts +49 -0
- package/src/protocol/bridgeEvents.ts +9 -0
- 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,33 @@ 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 MANAGER_FIELD = /^(npm|pnpm|yarn|bun)@(\d+)/;
|
|
57
|
+
|
|
58
|
+
const ENGINE_MANAGERS: readonly string[] = [
|
|
59
|
+
"pnpm",
|
|
60
|
+
"bun",
|
|
61
|
+
"yarn",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const YARN_BERRY = "yarn-berry";
|
|
65
|
+
|
|
66
|
+
const VERSION_NUMBER = /^v?(\d+(?:\.\d+)*)/;
|
|
67
|
+
|
|
68
|
+
const RANGE_COMPARATOR = /^(>=|<=|>|<|=|\^|~)?v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/;
|
|
69
|
+
|
|
70
|
+
const HYPHEN_RANGE = /(\S+)\s+-\s+(\S+)/g;
|
|
53
71
|
|
|
54
72
|
const TOKEN_KEY = /TOKEN/i;
|
|
55
73
|
|
|
@@ -71,6 +89,14 @@ const MANAGERS: Readonly<Record<string, BotPackageManager>> = {
|
|
|
71
89
|
npm: BotPackageManager.Npm,
|
|
72
90
|
pnpm: BotPackageManager.Pnpm,
|
|
73
91
|
yarn: BotPackageManager.Yarn,
|
|
92
|
+
[YARN_BERRY]: BotPackageManager.Yarn,
|
|
93
|
+
bun: BotPackageManager.Bun,
|
|
94
|
+
requirements: BotPackageManager.Pip,
|
|
95
|
+
uv: BotPackageManager.Uv,
|
|
96
|
+
poetry: BotPackageManager.Pip,
|
|
97
|
+
pdm: BotPackageManager.Pip,
|
|
98
|
+
pipenv: BotPackageManager.Pipenv,
|
|
99
|
+
project: BotPackageManager.Pip,
|
|
74
100
|
};
|
|
75
101
|
|
|
76
102
|
export interface PythonRequirement {
|
|
@@ -166,6 +192,8 @@ const FIXED_FILES: readonly string[] = [
|
|
|
166
192
|
DETECT_TABLE.procfile.file,
|
|
167
193
|
SERVERK_JSON,
|
|
168
194
|
...ENV_EXAMPLE_FILES,
|
|
195
|
+
...DETECT_TABLE.node.versionFiles.map((entry) => entry.file),
|
|
196
|
+
...DETECT_TABLE.python.versionFiles.map((entry) => entry.file),
|
|
169
197
|
];
|
|
170
198
|
|
|
171
199
|
const sourceRank = (path: string) => {
|
|
@@ -425,13 +453,29 @@ const nodeStartOf = (packageJson: PackageJson | null, has: (path: string) => boo
|
|
|
425
453
|
: null;
|
|
426
454
|
};
|
|
427
455
|
|
|
428
|
-
const pythonStartOf = (has: (path: string) => boolean): DetectedCommand | null => {
|
|
456
|
+
const pythonStartOf = (has: (path: string) => boolean, paths: ReadonlySet<string>): DetectedCommand | null => {
|
|
429
457
|
const file = DETECT_TABLE.python.startFiles.find(has);
|
|
430
458
|
|
|
431
|
-
|
|
459
|
+
if (file) {
|
|
460
|
+
return {
|
|
461
|
+
command: `python ${file}`,
|
|
462
|
+
source: file,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const packages = [
|
|
467
|
+
...paths,
|
|
468
|
+
]
|
|
469
|
+
.map((path) => PYTHON_PACKAGE_MAIN.exec(path)?.[1])
|
|
470
|
+
.filter((name): name is string => name !== undefined && !skipped(name) && !NOT_A_BOT_PACKAGE.has(name))
|
|
471
|
+
.sort(compareCodePoints);
|
|
472
|
+
|
|
473
|
+
const name = packages.length === 1 ? packages[0] : null;
|
|
474
|
+
|
|
475
|
+
return name
|
|
432
476
|
? {
|
|
433
|
-
command: `python ${
|
|
434
|
-
source:
|
|
477
|
+
command: `python -m ${name}`,
|
|
478
|
+
source: `${name}/__main__.py`,
|
|
435
479
|
}
|
|
436
480
|
: null;
|
|
437
481
|
};
|
|
@@ -442,6 +486,243 @@ const nodeStartFileOf = (packageJson: PackageJson | null, has: (path: string) =>
|
|
|
442
486
|
return (main !== null && has(main)) || DETECT_TABLE.node.startFiles.some(has);
|
|
443
487
|
};
|
|
444
488
|
|
|
489
|
+
interface NodeManager {
|
|
490
|
+
key: string;
|
|
491
|
+
locked: boolean;
|
|
492
|
+
source: string;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const lockfileOf = (manager: string, has: (path: string) => boolean) => {
|
|
496
|
+
const lockManager = manager === YARN_BERRY ? "yarn" : manager;
|
|
497
|
+
|
|
498
|
+
return DETECT_TABLE.node.lockfiles.find((entry) => entry.manager === lockManager && has(entry.file)) ?? null;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const nodeManagerOf = (packageJson: PackageJson | null, has: (path: string) => boolean): NodeManager => {
|
|
502
|
+
const declared = packageJson?.[DETECT_TABLE.node.managerField];
|
|
503
|
+
const field = typeof declared === "string" ? MANAGER_FIELD.exec(declared.trim()) : null;
|
|
504
|
+
|
|
505
|
+
if (field) {
|
|
506
|
+
const key = field[1] === "yarn" && Number(field[2]) >= 2 ? YARN_BERRY : (field[1] ?? "npm");
|
|
507
|
+
const lock = lockfileOf(key, has);
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
key,
|
|
511
|
+
locked: lock !== null,
|
|
512
|
+
source: lock?.file ?? DETECT_TABLE.node.manifest,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (const entry of DETECT_TABLE.node.lockfiles) {
|
|
517
|
+
if (entry.manager !== "npm" && has(entry.file)) {
|
|
518
|
+
return {
|
|
519
|
+
key: entry.manager === "yarn" && has(DETECT_TABLE.node.berryFile) ? YARN_BERRY : entry.manager,
|
|
520
|
+
locked: true,
|
|
521
|
+
source: entry.file,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (has(DETECT_TABLE.node.berryFile)) {
|
|
527
|
+
return {
|
|
528
|
+
key: YARN_BERRY,
|
|
529
|
+
locked: false,
|
|
530
|
+
source: DETECT_TABLE.node.berryFile,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const engines = recordOf(packageJson?.engines);
|
|
535
|
+
const engine = ENGINE_MANAGERS.find((manager) => typeof engines[manager] === "string") ?? null;
|
|
536
|
+
|
|
537
|
+
if (engine) {
|
|
538
|
+
return {
|
|
539
|
+
key: engine,
|
|
540
|
+
locked: false,
|
|
541
|
+
source: DETECT_TABLE.node.manifest,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const npmLock = lockfileOf("npm", has);
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
key: "npm",
|
|
549
|
+
locked: npmLock !== null,
|
|
550
|
+
source: npmLock?.file ?? DETECT_TABLE.node.manifest,
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const nodeBuildOf = (packageJson: PackageJson | null, manager: string): DetectedCommand | null => {
|
|
555
|
+
const script = recordOf(packageJson?.scripts)[DETECT_TABLE.node.buildScript];
|
|
556
|
+
|
|
557
|
+
if (typeof script !== "string" || script.trim().length === 0) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const run = DETECT_TABLE.node.runCommands[manager] ?? DETECT_TABLE.node.runCommands.npm ?? [];
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
command: [
|
|
565
|
+
...run,
|
|
566
|
+
DETECT_TABLE.node.buildScript,
|
|
567
|
+
].join(" "),
|
|
568
|
+
source: DETECT_TABLE.node.manifest,
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const versionFromFile = (text: string, spec: DetectVersionFile): string | null => {
|
|
573
|
+
for (const raw of text.split("\n")) {
|
|
574
|
+
let line = raw.trim();
|
|
575
|
+
|
|
576
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (spec.tool !== undefined) {
|
|
581
|
+
const [tool, ...rest] = line.split(/\s+/);
|
|
582
|
+
|
|
583
|
+
if (tool !== spec.tool) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
line = rest[0] ?? "";
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (spec.key !== undefined) {
|
|
591
|
+
const match = new RegExp(`^${spec.key}\\s*=\\s*"?([^"\\s]+)`).exec(line);
|
|
592
|
+
|
|
593
|
+
if (!match) {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
line = match[1] ?? "";
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (spec.prefix !== undefined) {
|
|
601
|
+
if (!line.startsWith(spec.prefix)) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
line = line.slice(spec.prefix.length);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return VERSION_NUMBER.exec(line)?.[1] ?? null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return null;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const versionFromFiles = (
|
|
615
|
+
specs: readonly DetectVersionFile[],
|
|
616
|
+
read: (path: string) => string | null,
|
|
617
|
+
parts: number,
|
|
618
|
+
): string | null => {
|
|
619
|
+
for (const spec of specs) {
|
|
620
|
+
const text = read(spec.file);
|
|
621
|
+
|
|
622
|
+
if (text === null) {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const version = versionFromFile(text, spec);
|
|
627
|
+
|
|
628
|
+
if (version !== null) {
|
|
629
|
+
return version.split(".").slice(0, parts).join(".");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return null;
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const comparatorAdmits = (comparator: string, major: number): boolean | null => {
|
|
637
|
+
const match = RANGE_COMPARATOR.exec(comparator);
|
|
638
|
+
|
|
639
|
+
if (!match) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const base = Number(match[2]);
|
|
644
|
+
const minor = match[3];
|
|
645
|
+
|
|
646
|
+
switch (match[1] ?? "") {
|
|
647
|
+
case ">=":
|
|
648
|
+
return major >= base;
|
|
649
|
+
case ">":
|
|
650
|
+
return minor === undefined ? major > base : major >= base;
|
|
651
|
+
case "<":
|
|
652
|
+
return major < base || (major === base && minor !== undefined && minor !== "0");
|
|
653
|
+
case "<=":
|
|
654
|
+
return major <= base;
|
|
655
|
+
default:
|
|
656
|
+
return major === base;
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const comparatorsOf = (clause: string) => {
|
|
661
|
+
return clause
|
|
662
|
+
.replace(HYPHEN_RANGE, ">=$1 <=$2")
|
|
663
|
+
.trim()
|
|
664
|
+
.split(/\s+/)
|
|
665
|
+
.filter((token) => token.length > 0);
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
const rangeAdmits = (range: string, major: number) => {
|
|
669
|
+
return range.split("||").some((clause) => {
|
|
670
|
+
const verdicts = comparatorsOf(clause).map((token) => comparatorAdmits(token, major));
|
|
671
|
+
|
|
672
|
+
return verdicts.length === 0 || verdicts.some((verdict) => verdict === null) || verdicts.every(Boolean);
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const requestedMajorOf = (range: string): string | null => {
|
|
677
|
+
const comparators = range
|
|
678
|
+
.split("||")
|
|
679
|
+
.flatMap(comparatorsOf)
|
|
680
|
+
.map((token) => RANGE_COMPARATOR.exec(token))
|
|
681
|
+
.filter((match) => match !== null);
|
|
682
|
+
const lower = comparators.filter((match) => match[1] !== "<" && match[1] !== "<=").map((match) => Number(match[2]));
|
|
683
|
+
|
|
684
|
+
if (lower.length > 0) {
|
|
685
|
+
return String(Math.min(...lower));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const upper = comparators.map((match) => (match[1] === "<" ? Number(match[2]) - 1 : Number(match[2])));
|
|
689
|
+
|
|
690
|
+
return upper.length === 0 ? null : String(Math.max(...upper));
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
interface NodeRequirement {
|
|
694
|
+
version: string;
|
|
695
|
+
unmet: boolean;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const nodeRequirementOf = (
|
|
699
|
+
packageJson: PackageJson | null,
|
|
700
|
+
read: (path: string) => string | null,
|
|
701
|
+
): NodeRequirement | null => {
|
|
702
|
+
const image = Number(DETECT_TABLE.node.defaultVersion);
|
|
703
|
+
const engine = recordOf(packageJson?.engines)[DETECT_TABLE.node.enginesKey];
|
|
704
|
+
|
|
705
|
+
if (typeof engine === "string" && engine.trim().length > 0) {
|
|
706
|
+
const version = requestedMajorOf(engine);
|
|
707
|
+
|
|
708
|
+
if (version !== null) {
|
|
709
|
+
return {
|
|
710
|
+
version,
|
|
711
|
+
unmet: !rangeAdmits(engine, image),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const version = versionFromFiles(DETECT_TABLE.node.versionFiles, read, 1);
|
|
717
|
+
|
|
718
|
+
return version === null
|
|
719
|
+
? null
|
|
720
|
+
: {
|
|
721
|
+
version,
|
|
722
|
+
unmet: Number(version) !== image,
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
|
|
445
726
|
const decideRuntime = (
|
|
446
727
|
node: {
|
|
447
728
|
manifest: boolean;
|
|
@@ -536,9 +817,13 @@ const committedWarnings = (paths: ReadonlySet<string>, warnings: Set<BotInspectW
|
|
|
536
817
|
|
|
537
818
|
const name = segments.at(-1) ?? "";
|
|
538
819
|
|
|
539
|
-
if (!skipped(path) &&
|
|
820
|
+
if (!skipped(path) && isSecretEnvFile(name)) {
|
|
540
821
|
warnings.add(BotInspectWarning.EnvCommitted);
|
|
541
822
|
}
|
|
823
|
+
|
|
824
|
+
if (!skipped(path) && DATABASE_FILE.test(name)) {
|
|
825
|
+
warnings.add(BotInspectWarning.DatabaseCommitted);
|
|
826
|
+
}
|
|
542
827
|
}
|
|
543
828
|
};
|
|
544
829
|
|
|
@@ -657,7 +942,6 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
657
942
|
const packageText = read(DETECT_TABLE.node.manifest);
|
|
658
943
|
const packageJson = packageText === null ? null : parseObject(packageText);
|
|
659
944
|
const hasPackage = has(DETECT_TABLE.node.manifest);
|
|
660
|
-
const nodeLock = DETECT_TABLE.node.lockfiles.find((entry) => has(entry.file)) ?? null;
|
|
661
945
|
const pythonManifest = DETECT_TABLE.python.manifests.find((entry) => has(entry.file)) ?? null;
|
|
662
946
|
const requirementsText = read(REQUIREMENTS_FILE);
|
|
663
947
|
const pyprojectText = read(PYPROJECT_FILE);
|
|
@@ -716,37 +1000,54 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
716
1000
|
|
|
717
1001
|
let packageManager: BotPackageManager | null = null;
|
|
718
1002
|
let install: DetectedCommand | null = null;
|
|
1003
|
+
let build: DetectedCommand | null = null;
|
|
1004
|
+
let nodeVersion: string | null = null;
|
|
1005
|
+
let pythonVersion: string | null = null;
|
|
719
1006
|
|
|
720
1007
|
if (runtime === BotRuntime.Node) {
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
packageManager = BotPackageManager.Npm;
|
|
1008
|
+
if (hasPackage) {
|
|
1009
|
+
const manager = nodeManagerOf(packageJson, has);
|
|
1010
|
+
const commands = manager.locked
|
|
1011
|
+
? DETECT_TABLE.node.installCommands.locked
|
|
1012
|
+
: DETECT_TABLE.node.installCommands.unlocked;
|
|
1013
|
+
|
|
1014
|
+
packageManager = MANAGERS[manager.key] ?? null;
|
|
729
1015
|
install = {
|
|
730
|
-
command: (
|
|
731
|
-
source:
|
|
1016
|
+
command: (commands[manager.key] ?? []).join(" "),
|
|
1017
|
+
source: manager.source,
|
|
732
1018
|
};
|
|
733
|
-
|
|
1019
|
+
build = nodeBuildOf(packageJson, manager.key);
|
|
1020
|
+
|
|
1021
|
+
if (!manager.locked) {
|
|
1022
|
+
warnings.add(BotInspectWarning.NoLockfile);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const requirement = nodeRequirementOf(packageJson, read);
|
|
1026
|
+
|
|
1027
|
+
nodeVersion = requirement?.version ?? null;
|
|
1028
|
+
|
|
1029
|
+
if (requirement?.unmet) {
|
|
1030
|
+
warnings.add(BotInspectWarning.NodeVersionUnmet);
|
|
1031
|
+
}
|
|
734
1032
|
} else {
|
|
735
1033
|
warnings.add(BotInspectWarning.NoManifest);
|
|
736
1034
|
}
|
|
737
1035
|
}
|
|
738
1036
|
|
|
739
1037
|
if (runtime === BotRuntime.Python) {
|
|
1038
|
+
pythonVersion = versionFromFiles(DETECT_TABLE.python.versionFiles, read, 2);
|
|
1039
|
+
|
|
740
1040
|
if (pythonManifest) {
|
|
741
|
-
packageManager =
|
|
1041
|
+
packageManager = MANAGERS[pythonManifest.manager] ?? null;
|
|
742
1042
|
install = {
|
|
743
|
-
command: [
|
|
744
|
-
"pip",
|
|
745
|
-
...(DETECT_TABLE.python.installArguments[pythonManifest.manager] ?? []),
|
|
746
|
-
].join(" "),
|
|
1043
|
+
command: (DETECT_TABLE.python.installCommands[pythonManifest.manager] ?? []).join(" "),
|
|
747
1044
|
source: pythonManifest.file,
|
|
748
1045
|
};
|
|
749
1046
|
|
|
1047
|
+
if (DETECT_TABLE.python.unhonouredManagers.includes(pythonManifest.manager)) {
|
|
1048
|
+
warnings.add(BotInspectWarning.LockfileNotHonoured);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
750
1051
|
const declared = pythonManifest.file === REQUIREMENTS_FILE ? requirements : pyproject;
|
|
751
1052
|
|
|
752
1053
|
if (declared.some((requirement) => !requirement.pinned)) {
|
|
@@ -768,28 +1069,31 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
768
1069
|
};
|
|
769
1070
|
}
|
|
770
1071
|
|
|
1072
|
+
if (serverk?.build) {
|
|
1073
|
+
build = {
|
|
1074
|
+
command: serverk.build,
|
|
1075
|
+
source: SERVERK_JSON,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
771
1079
|
const procfile = procfileCommand(read(DETECT_TABLE.procfile.file));
|
|
772
1080
|
const detectedStart =
|
|
773
1081
|
runtime === BotRuntime.Node
|
|
774
1082
|
? nodeStartOf(packageJson, has)
|
|
775
1083
|
: runtime === BotRuntime.Python
|
|
776
|
-
? pythonStartOf(has)
|
|
1084
|
+
? pythonStartOf(has, paths)
|
|
777
1085
|
: null;
|
|
778
1086
|
const start: DetectedCommand | null = serverk?.start
|
|
779
1087
|
? {
|
|
780
1088
|
command: serverk.start,
|
|
781
1089
|
source: SERVERK_JSON,
|
|
782
1090
|
}
|
|
783
|
-
: (
|
|
1091
|
+
: (procfile ?? detectedStart);
|
|
784
1092
|
|
|
785
1093
|
if (!start) {
|
|
786
1094
|
warnings.add(BotInspectWarning.NoStart);
|
|
787
1095
|
}
|
|
788
1096
|
|
|
789
|
-
if (runtime === BotRuntime.Node && start && TYPESCRIPT_ENTRY.test(start.command)) {
|
|
790
|
-
warnings.add(BotInspectWarning.TypescriptEntry);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
1097
|
committedWarnings(paths, warnings);
|
|
794
1098
|
|
|
795
1099
|
const env = envCollector(warnings);
|
|
@@ -806,6 +1110,10 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
806
1110
|
}
|
|
807
1111
|
}
|
|
808
1112
|
|
|
1113
|
+
for (const entry of input.skippedEnv === undefined ? [] : parseEnvExample(input.skippedEnv)) {
|
|
1114
|
+
env.add(entry.key, ENV_FILE, null, null);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
809
1117
|
for (const [path, text] of sources) {
|
|
810
1118
|
for (const key of scanEnvKeys(path, text)) {
|
|
811
1119
|
env.add(key, path, null, null);
|
|
@@ -834,7 +1142,10 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
834
1142
|
framework,
|
|
835
1143
|
platform,
|
|
836
1144
|
install,
|
|
1145
|
+
build,
|
|
837
1146
|
start,
|
|
1147
|
+
nodeVersion,
|
|
1148
|
+
pythonVersion,
|
|
838
1149
|
env: env.entries,
|
|
839
1150
|
tokenKey: tokenKeyOf(platform, env.entries),
|
|
840
1151
|
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", () => {
|