@serverkgg/bridge 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/rules/package.md +2 -1
- package/src/bot/botDeploy.test.ts +57 -0
- package/src/bot/botEnvironment.test.ts +21 -0
- package/src/bot/botEnvironment.ts +10 -0
- package/src/bot/botRun.test.ts +296 -0
- package/src/bot/botRun.ts +109 -0
- package/src/bot/botState.test.ts +34 -1
- package/src/bot/botState.ts +1 -0
- package/src/bot/botVariables.test.ts +22 -0
- package/src/bot/botVariables.ts +3 -0
- package/src/bot/detect/detect.test.ts +172 -0
- package/src/bot/detect/detect.ts +31 -0
- package/src/bot/detect/detectResult.ts +1 -0
- package/src/bot/detect/detectTable.test.ts +25 -2
- package/src/bot/detect/detectTable.ts +24 -1
- package/src/bot/detect/keys.test.ts +7 -0
- package/src/bot/detect/keys.ts +3 -0
- package/src/bot/detect/serverkJson.test.ts +21 -0
- package/src/bot/detect/serverkJson.ts +13 -1
- package/src/bot/index.ts +1 -0
- package/src/cli/link.ts +3 -3
- package/src/cli/main.ts +8 -0
- package/src/cli/readme.test.ts +470 -0
- package/src/cli/readme.ts +570 -0
- package/src/cli/validate.test.ts +41 -1
- package/src/cli/validate.ts +25 -0
- package/src/context/bridgeContext.ts +1 -0
- package/src/modules/bridgeModule.ts +1 -0
- package/src/protocol/bridgeManifest.ts +1 -0
- package/src/runtime/bridgeContext.test.ts +6 -0
- package/src/runtime/bridgeContext.ts +7 -2
- package/src/runtime/bridgeExec.test.ts +150 -0
- package/src/runtime/bridgeExec.ts +67 -19
- package/src/runtime/bridgeLog.ts +9 -0
- package/src/runtime/bridgeRedaction.test.ts +28 -2
- package/src/runtime/bridgeRedaction.ts +7 -1
- package/src/runtime/bridgeRegistry.test.ts +30 -0
- package/src/runtime/bridgeRegistry.ts +1 -0
- package/src/runtime/bridgeScopedContext.ts +3 -0
- package/src/runtime/bridgeSetupHandlers.test.ts +3 -0
- package/src/runtime/bridgeSupervisor.ts +2 -6
- package/src/runtime/serve.ts +9 -2
- package/src/wire/panel.test.ts +32 -0
- package/src/wire/panel.ts +1 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
DEPLOYMENT_ID,
|
|
18
18
|
GIT_TOKEN,
|
|
19
19
|
INSTALL_COMMAND,
|
|
20
|
+
PRE_DEPLOY_COMMAND,
|
|
20
21
|
REPO_BRANCH,
|
|
21
22
|
REPO_REF,
|
|
22
23
|
REPO_URL,
|
|
@@ -47,6 +48,7 @@ describe("readBotConfig", () => {
|
|
|
47
48
|
[REPO_BRANCH]: "dev",
|
|
48
49
|
[START_COMMAND]: "node src/bot.js",
|
|
49
50
|
[INSTALL_COMMAND]: "npm install --legacy-peer-deps",
|
|
51
|
+
[PRE_DEPLOY_COMMAND]: " npx prisma migrate deploy ",
|
|
50
52
|
[ROOT_DIRECTORY]: " apps/bot ",
|
|
51
53
|
[DEPLOYMENT_ID]: "dep_a1b2c3d4",
|
|
52
54
|
[SNAPSHOT_ID]: "snap_1",
|
|
@@ -65,6 +67,7 @@ describe("readBotConfig", () => {
|
|
|
65
67
|
branch: "dev",
|
|
66
68
|
startCommand: "node src/bot.js",
|
|
67
69
|
installCommand: "npm install --legacy-peer-deps",
|
|
70
|
+
preDeployCommand: "npx prisma migrate deploy",
|
|
68
71
|
rootDirectory: "apps/bot",
|
|
69
72
|
deployment: "dep_a1b2c3d4",
|
|
70
73
|
snapshot: "snap_1",
|
|
@@ -88,6 +91,7 @@ describe("readBotConfig", () => {
|
|
|
88
91
|
branch: DEFAULT_REPO_BRANCH,
|
|
89
92
|
startCommand: null,
|
|
90
93
|
installCommand: null,
|
|
94
|
+
preDeployCommand: null,
|
|
91
95
|
rootDirectory: null,
|
|
92
96
|
deployment: null,
|
|
93
97
|
snapshot: null,
|
|
@@ -96,6 +100,24 @@ describe("readBotConfig", () => {
|
|
|
96
100
|
});
|
|
97
101
|
});
|
|
98
102
|
|
|
103
|
+
it("reads no pre-deploy command from a server an older platform configured without the key", () => {
|
|
104
|
+
expect(
|
|
105
|
+
readBotConfig(
|
|
106
|
+
contextWith({
|
|
107
|
+
[START_COMMAND]: "node .",
|
|
108
|
+
[INSTALL_COMMAND]: "npm ci",
|
|
109
|
+
}),
|
|
110
|
+
).preDeployCommand,
|
|
111
|
+
).toBeNull();
|
|
112
|
+
expect(
|
|
113
|
+
readBotConfig(
|
|
114
|
+
contextWith({
|
|
115
|
+
[PRE_DEPLOY_COMMAND]: " ",
|
|
116
|
+
}),
|
|
117
|
+
).preDeployCommand,
|
|
118
|
+
).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
|
|
99
121
|
it("drops a web address that is not a bare https host", () => {
|
|
100
122
|
for (const value of [
|
|
101
123
|
"http://my-bot.srvk.gg",
|
package/src/bot/botVariables.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
GIT_TOKEN,
|
|
7
7
|
INSTALL_COMMAND,
|
|
8
8
|
isBotPlatform,
|
|
9
|
+
PRE_DEPLOY_COMMAND,
|
|
9
10
|
REPO_BRANCH,
|
|
10
11
|
REPO_REF,
|
|
11
12
|
REPO_URL,
|
|
@@ -46,6 +47,7 @@ export interface BotConfig {
|
|
|
46
47
|
branch: string;
|
|
47
48
|
startCommand: string | null;
|
|
48
49
|
installCommand: string | null;
|
|
50
|
+
preDeployCommand: string | null;
|
|
49
51
|
rootDirectory: string | null;
|
|
50
52
|
deployment: string | null;
|
|
51
53
|
snapshot: string | null;
|
|
@@ -73,6 +75,7 @@ export const readBotConfig = (context: Pick<BridgeContext, "variable" | "secret"
|
|
|
73
75
|
branch: present(context.variable(REPO_BRANCH)) ?? DEFAULT_REPO_BRANCH,
|
|
74
76
|
startCommand: present(context.variable(START_COMMAND)),
|
|
75
77
|
installCommand: present(context.variable(INSTALL_COMMAND)),
|
|
78
|
+
preDeployCommand: present(context.variable(PRE_DEPLOY_COMMAND)),
|
|
76
79
|
rootDirectory: present(context.variable(ROOT_DIRECTORY)),
|
|
77
80
|
deployment: present(context.variable(DEPLOYMENT_ID)),
|
|
78
81
|
snapshot: present(context.variable(SNAPSHOT_ID)),
|
|
@@ -79,6 +79,7 @@ describe("detect on the templates", () => {
|
|
|
79
79
|
source: "package-lock.json",
|
|
80
80
|
},
|
|
81
81
|
build: null,
|
|
82
|
+
preDeploy: null,
|
|
82
83
|
start: {
|
|
83
84
|
command: "node index.js",
|
|
84
85
|
source: "package.json",
|
|
@@ -110,6 +111,7 @@ describe("detect on the templates", () => {
|
|
|
110
111
|
source: "requirements.txt",
|
|
111
112
|
},
|
|
112
113
|
build: null,
|
|
114
|
+
preDeploy: null,
|
|
113
115
|
start: {
|
|
114
116
|
command: "python main.py",
|
|
115
117
|
source: "main.py",
|
|
@@ -1045,6 +1047,176 @@ describe("detect with serverk.json", () => {
|
|
|
1045
1047
|
});
|
|
1046
1048
|
});
|
|
1047
1049
|
|
|
1050
|
+
describe("detect pre-deploy commands", () => {
|
|
1051
|
+
const PRISMA_NODE = {
|
|
1052
|
+
...DISCORD_NODE,
|
|
1053
|
+
"package.json": JSON.stringify({
|
|
1054
|
+
name: "discord-bot",
|
|
1055
|
+
main: "index.js",
|
|
1056
|
+
scripts: {
|
|
1057
|
+
start: "node index.js",
|
|
1058
|
+
migrate: "prisma migrate deploy",
|
|
1059
|
+
},
|
|
1060
|
+
dependencies: {
|
|
1061
|
+
"discord.js": "^14.27.0",
|
|
1062
|
+
"@prisma/client": "^6.0.0",
|
|
1063
|
+
},
|
|
1064
|
+
devDependencies: {
|
|
1065
|
+
prisma: "^6.0.0",
|
|
1066
|
+
},
|
|
1067
|
+
}),
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
it("migrates a Prisma project that ships its migrations", () => {
|
|
1071
|
+
expect(
|
|
1072
|
+
project(PRISMA_NODE, [
|
|
1073
|
+
"package-lock.json",
|
|
1074
|
+
"prisma/schema.prisma",
|
|
1075
|
+
"prisma/migrations/migration_lock.toml",
|
|
1076
|
+
"prisma/migrations/20260101000000_init/migration.sql",
|
|
1077
|
+
]).preDeploy,
|
|
1078
|
+
).toEqual({
|
|
1079
|
+
command: "prisma migrate deploy",
|
|
1080
|
+
source: "prisma/migrations/migration_lock.toml",
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
it("runs nothing for a Prisma project with no migrations folder, the db push workflow", () => {
|
|
1085
|
+
expect(
|
|
1086
|
+
project(PRISMA_NODE, [
|
|
1087
|
+
"package-lock.json",
|
|
1088
|
+
"prisma/schema.prisma",
|
|
1089
|
+
]).preDeploy,
|
|
1090
|
+
).toBeNull();
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
it("never reads a migrate script in package.json as a pre-deploy command", () => {
|
|
1094
|
+
expect(
|
|
1095
|
+
project(
|
|
1096
|
+
{
|
|
1097
|
+
...DISCORD_NODE,
|
|
1098
|
+
"package.json": JSON.stringify({
|
|
1099
|
+
main: "index.js",
|
|
1100
|
+
scripts: {
|
|
1101
|
+
start: "node index.js",
|
|
1102
|
+
migrate: "knex migrate:latest",
|
|
1103
|
+
},
|
|
1104
|
+
dependencies: {
|
|
1105
|
+
"discord.js": "^14.27.0",
|
|
1106
|
+
knex: "^3.1.0",
|
|
1107
|
+
},
|
|
1108
|
+
}),
|
|
1109
|
+
},
|
|
1110
|
+
[
|
|
1111
|
+
"package-lock.json",
|
|
1112
|
+
"knexfile.js",
|
|
1113
|
+
"migrations/20260101_init.js",
|
|
1114
|
+
"drizzle.config.ts",
|
|
1115
|
+
],
|
|
1116
|
+
).preDeploy,
|
|
1117
|
+
).toBeNull();
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
it("upgrades an Alembic project to its head", () => {
|
|
1121
|
+
expect(
|
|
1122
|
+
project(TELEGRAM_PYTHON, [
|
|
1123
|
+
"alembic.ini",
|
|
1124
|
+
"alembic/env.py",
|
|
1125
|
+
]).preDeploy,
|
|
1126
|
+
).toEqual({
|
|
1127
|
+
command: "alembic upgrade head",
|
|
1128
|
+
source: "alembic.ini",
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
it("migrates a Django project without asking", () => {
|
|
1133
|
+
expect(
|
|
1134
|
+
project(TELEGRAM_PYTHON, [
|
|
1135
|
+
"manage.py",
|
|
1136
|
+
]).preDeploy,
|
|
1137
|
+
).toEqual({
|
|
1138
|
+
command: "python manage.py migrate --noinput",
|
|
1139
|
+
source: "manage.py",
|
|
1140
|
+
});
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
it("prefers Alembic when a project ships both alembic.ini and manage.py", () => {
|
|
1144
|
+
expect(
|
|
1145
|
+
project(TELEGRAM_PYTHON, [
|
|
1146
|
+
"manage.py",
|
|
1147
|
+
"alembic.ini",
|
|
1148
|
+
]).preDeploy,
|
|
1149
|
+
).toEqual({
|
|
1150
|
+
command: "alembic upgrade head",
|
|
1151
|
+
source: "alembic.ini",
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
it("reads each rule only for its own runtime", () => {
|
|
1156
|
+
expect(
|
|
1157
|
+
project(DISCORD_NODE, [
|
|
1158
|
+
"package-lock.json",
|
|
1159
|
+
"alembic.ini",
|
|
1160
|
+
"manage.py",
|
|
1161
|
+
]).preDeploy,
|
|
1162
|
+
).toBeNull();
|
|
1163
|
+
expect(
|
|
1164
|
+
project(TELEGRAM_PYTHON, [
|
|
1165
|
+
"prisma/migrations/migration_lock.toml",
|
|
1166
|
+
]).preDeploy,
|
|
1167
|
+
).toBeNull();
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
it("runs nothing for a bot with no migration tool", () => {
|
|
1171
|
+
expect(project(DISCORD_NODE).preDeploy).toBeNull();
|
|
1172
|
+
expect(project(TELEGRAM_PYTHON).preDeploy).toBeNull();
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it("lets serverk.json name the pre-deploy command over the detected one", () => {
|
|
1176
|
+
expect(
|
|
1177
|
+
project(
|
|
1178
|
+
{
|
|
1179
|
+
...TELEGRAM_PYTHON,
|
|
1180
|
+
"serverk.json": JSON.stringify({
|
|
1181
|
+
preDeploy: "python -m bot.migrate",
|
|
1182
|
+
}),
|
|
1183
|
+
},
|
|
1184
|
+
[
|
|
1185
|
+
"alembic.ini",
|
|
1186
|
+
],
|
|
1187
|
+
).preDeploy,
|
|
1188
|
+
).toEqual({
|
|
1189
|
+
command: "python -m bot.migrate",
|
|
1190
|
+
source: "serverk.json",
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it("falls back to the detected command when serverk.json names none or is broken", () => {
|
|
1195
|
+
for (const text of [
|
|
1196
|
+
JSON.stringify({
|
|
1197
|
+
start: "python main.py",
|
|
1198
|
+
}),
|
|
1199
|
+
'{"preDeploy": ""}',
|
|
1200
|
+
]) {
|
|
1201
|
+
expect(
|
|
1202
|
+
project(
|
|
1203
|
+
{
|
|
1204
|
+
...TELEGRAM_PYTHON,
|
|
1205
|
+
"serverk.json": text,
|
|
1206
|
+
},
|
|
1207
|
+
[
|
|
1208
|
+
"manage.py",
|
|
1209
|
+
],
|
|
1210
|
+
).preDeploy,
|
|
1211
|
+
text,
|
|
1212
|
+
).toEqual({
|
|
1213
|
+
command: "python manage.py migrate --noinput",
|
|
1214
|
+
source: "manage.py",
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1048
1220
|
describe("compareCodePoints", () => {
|
|
1049
1221
|
it("orders by code point, the way Python sorts strings, whatever the locale", () => {
|
|
1050
1222
|
expect(
|
package/src/bot/detect/detect.ts
CHANGED
|
@@ -592,6 +592,34 @@ const nodeBuildOf = (packageJson: PackageJson | null, manager: string): Detected
|
|
|
592
592
|
};
|
|
593
593
|
};
|
|
594
594
|
|
|
595
|
+
const preDeployOf = (
|
|
596
|
+
serverk: ServerkJson | null,
|
|
597
|
+
runtime: BotRuntime | null,
|
|
598
|
+
has: (path: string) => boolean,
|
|
599
|
+
): DetectedCommand | null => {
|
|
600
|
+
if (serverk?.preDeploy) {
|
|
601
|
+
return {
|
|
602
|
+
command: serverk.preDeploy,
|
|
603
|
+
source: SERVERK_JSON,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const rules =
|
|
608
|
+
runtime === BotRuntime.Node
|
|
609
|
+
? DETECT_TABLE.node.preDeployCommands
|
|
610
|
+
: runtime === BotRuntime.Python
|
|
611
|
+
? DETECT_TABLE.python.preDeployCommands
|
|
612
|
+
: [];
|
|
613
|
+
const rule = rules.find((entry) => has(entry.file));
|
|
614
|
+
|
|
615
|
+
return rule
|
|
616
|
+
? {
|
|
617
|
+
command: rule.command,
|
|
618
|
+
source: rule.file,
|
|
619
|
+
}
|
|
620
|
+
: null;
|
|
621
|
+
};
|
|
622
|
+
|
|
595
623
|
const versionFromFile = (
|
|
596
624
|
text: string,
|
|
597
625
|
spec: DetectVersionFile,
|
|
@@ -1117,6 +1145,8 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
1117
1145
|
};
|
|
1118
1146
|
}
|
|
1119
1147
|
|
|
1148
|
+
const preDeploy = preDeployOf(serverk, runtime, has);
|
|
1149
|
+
|
|
1120
1150
|
const procfile = procfileCommand(read(DETECT_TABLE.procfile.file));
|
|
1121
1151
|
const detectedStart =
|
|
1122
1152
|
runtime === BotRuntime.Node
|
|
@@ -1198,6 +1228,7 @@ export const detect = (input: DetectInput): DetectResult => {
|
|
|
1198
1228
|
platform,
|
|
1199
1229
|
install,
|
|
1200
1230
|
build,
|
|
1231
|
+
preDeploy,
|
|
1201
1232
|
start,
|
|
1202
1233
|
nodeVersion,
|
|
1203
1234
|
pythonVersion,
|
|
@@ -35,6 +35,7 @@ export interface DetectResult {
|
|
|
35
35
|
platform: BotPlatform | null;
|
|
36
36
|
install: DetectedCommand | null;
|
|
37
37
|
build: DetectedCommand | null;
|
|
38
|
+
preDeploy: DetectedCommand | null;
|
|
38
39
|
start: DetectedCommand | null;
|
|
39
40
|
nodeVersion: string | null;
|
|
40
41
|
pythonVersion: string | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { BotFramework } from "./botFramework";
|
|
3
|
-
import { DETECT_TABLE, renderDetectTable } from "./detectTable";
|
|
3
|
+
import { DETECT_TABLE, DETECT_TABLE_VERSION, renderDetectTable } from "./detectTable";
|
|
4
4
|
import { isEnvKey } from "./envKey";
|
|
5
5
|
import { parseServerkJson } from "./serverkJson";
|
|
6
6
|
|
|
@@ -9,8 +9,31 @@ describe("renderDetectTable", () => {
|
|
|
9
9
|
const rendered = renderDetectTable();
|
|
10
10
|
|
|
11
11
|
expect(rendered.endsWith("}\n")).toBe(true);
|
|
12
|
-
expect(
|
|
12
|
+
expect(DETECT_TABLE.version).toBe(DETECT_TABLE_VERSION);
|
|
13
|
+
expect(rendered).toContain('\t"version": 3,');
|
|
13
14
|
expect(JSON.parse(rendered)).toEqual(DETECT_TABLE);
|
|
15
|
+
expect(renderDetectTable()).toBe(rendered);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("renders the pre-deploy rules in order, each with the file that triggers it and its command", () => {
|
|
19
|
+
const rendered = JSON.parse(renderDetectTable());
|
|
20
|
+
|
|
21
|
+
expect(rendered.node.preDeployCommands).toEqual([
|
|
22
|
+
{
|
|
23
|
+
file: "prisma/migrations/migration_lock.toml",
|
|
24
|
+
command: "prisma migrate deploy",
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
expect(rendered.python.preDeployCommands).toEqual([
|
|
28
|
+
{
|
|
29
|
+
file: "alembic.ini",
|
|
30
|
+
command: "alembic upgrade head",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
file: "manage.py",
|
|
34
|
+
command: "python manage.py migrate --noinput",
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
14
37
|
});
|
|
15
38
|
|
|
16
39
|
it("names a locked and an unlocked install and a run command for every Node manager", () => {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
SERVERK_JSON_EXAMPLE_LENGTH_LIMIT,
|
|
10
10
|
} from "./serverkJson";
|
|
11
11
|
|
|
12
|
-
export const DETECT_TABLE_VERSION =
|
|
12
|
+
export const DETECT_TABLE_VERSION = 3;
|
|
13
13
|
|
|
14
14
|
export const DETECT_TABLE_FILE = "image/detect.json";
|
|
15
15
|
|
|
@@ -25,6 +25,11 @@ export interface DetectVersionFile {
|
|
|
25
25
|
key?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export interface DetectPreDeployCommand {
|
|
29
|
+
file: string;
|
|
30
|
+
command: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
export interface DetectProcfile {
|
|
29
34
|
file: string;
|
|
30
35
|
line: string;
|
|
@@ -56,6 +61,7 @@ export interface NodeDetectTable {
|
|
|
56
61
|
unlocked: Record<string, string[]>;
|
|
57
62
|
};
|
|
58
63
|
runCommands: Record<string, string[]>;
|
|
64
|
+
preDeployCommands: readonly DetectPreDeployCommand[];
|
|
59
65
|
versionFiles: DetectVersionFile[];
|
|
60
66
|
defaultVersion: string;
|
|
61
67
|
}
|
|
@@ -65,6 +71,7 @@ export interface PythonDetectTable {
|
|
|
65
71
|
manifests: DetectLockfile[];
|
|
66
72
|
installCommands: Record<string, string[]>;
|
|
67
73
|
unhonouredManagers: string[];
|
|
74
|
+
preDeployCommands: readonly DetectPreDeployCommand[];
|
|
68
75
|
versionFiles: DetectVersionFile[];
|
|
69
76
|
defaultVersion: string;
|
|
70
77
|
}
|
|
@@ -219,6 +226,12 @@ export const DETECT_TABLE: DetectTable = {
|
|
|
219
226
|
"run",
|
|
220
227
|
],
|
|
221
228
|
},
|
|
229
|
+
preDeployCommands: [
|
|
230
|
+
{
|
|
231
|
+
file: "prisma/migrations/migration_lock.toml",
|
|
232
|
+
command: "prisma migrate deploy",
|
|
233
|
+
},
|
|
234
|
+
],
|
|
222
235
|
versionFiles: [
|
|
223
236
|
{
|
|
224
237
|
file: ".nvmrc",
|
|
@@ -312,6 +325,16 @@ export const DETECT_TABLE: DetectTable = {
|
|
|
312
325
|
"poetry",
|
|
313
326
|
"pdm",
|
|
314
327
|
],
|
|
328
|
+
preDeployCommands: [
|
|
329
|
+
{
|
|
330
|
+
file: "alembic.ini",
|
|
331
|
+
command: "alembic upgrade head",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
file: "manage.py",
|
|
335
|
+
command: "python manage.py migrate --noinput",
|
|
336
|
+
},
|
|
337
|
+
],
|
|
315
338
|
versionFiles: [
|
|
316
339
|
{
|
|
317
340
|
file: ".python-version",
|
|
@@ -12,8 +12,10 @@ import {
|
|
|
12
12
|
BOT_RAW_PORT_KEYS,
|
|
13
13
|
BOT_WEB,
|
|
14
14
|
BOT_WEB_PORT_KEY,
|
|
15
|
+
INSTALL_COMMAND,
|
|
15
16
|
isReservedBotKey,
|
|
16
17
|
PORT,
|
|
18
|
+
PRE_DEPLOY_COMMAND,
|
|
17
19
|
SERVERK_PORT_1,
|
|
18
20
|
SERVERK_PORT_2,
|
|
19
21
|
SERVERK_WEB_HOST,
|
|
@@ -44,6 +46,11 @@ describe("isReservedBotKey", () => {
|
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
48
|
|
|
49
|
+
it("reserves the pre-deploy command right after the install command, so a customer cannot set it as a variable", () => {
|
|
50
|
+
expect(BOT_PLATFORM_KEYS.indexOf(PRE_DEPLOY_COMMAND)).toBe(BOT_PLATFORM_KEYS.indexOf(INSTALL_COMMAND) + 1);
|
|
51
|
+
expect(isReservedBotKey(PRE_DEPLOY_COMMAND)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
47
54
|
it("leaves PORT to the customer, so a bot without the web port keeps its own", () => {
|
|
48
55
|
expect(isReservedBotKey(PORT)).toBe(false);
|
|
49
56
|
});
|
package/src/bot/detect/keys.ts
CHANGED
|
@@ -10,6 +10,8 @@ export const START_COMMAND = "START_COMMAND";
|
|
|
10
10
|
|
|
11
11
|
export const INSTALL_COMMAND = "INSTALL_COMMAND";
|
|
12
12
|
|
|
13
|
+
export const PRE_DEPLOY_COMMAND = "PRE_DEPLOY_COMMAND";
|
|
14
|
+
|
|
13
15
|
export const ROOT_DIRECTORY = "ROOT_DIRECTORY";
|
|
14
16
|
|
|
15
17
|
export const DEPLOYMENT_ID = "DEPLOYMENT_ID";
|
|
@@ -67,6 +69,7 @@ export const BOT_PLATFORM_KEYS: readonly string[] = [
|
|
|
67
69
|
REPO_BRANCH,
|
|
68
70
|
START_COMMAND,
|
|
69
71
|
INSTALL_COMMAND,
|
|
72
|
+
PRE_DEPLOY_COMMAND,
|
|
70
73
|
ROOT_DIRECTORY,
|
|
71
74
|
DEPLOYMENT_ID,
|
|
72
75
|
SNAPSHOT_ID,
|
|
@@ -15,6 +15,7 @@ describe("parseServerkJson", () => {
|
|
|
15
15
|
framework: "discord-py",
|
|
16
16
|
start: " python -m bot ",
|
|
17
17
|
install: "pip install -r requirements/prod.txt",
|
|
18
|
+
preDeploy: " alembic upgrade head ",
|
|
18
19
|
env: [
|
|
19
20
|
"GUILD_ID",
|
|
20
21
|
{
|
|
@@ -31,6 +32,7 @@ describe("parseServerkJson", () => {
|
|
|
31
32
|
start: "python -m bot",
|
|
32
33
|
install: "pip install -r requirements/prod.txt",
|
|
33
34
|
build: null,
|
|
35
|
+
preDeploy: "alembic upgrade head",
|
|
34
36
|
env: [
|
|
35
37
|
{
|
|
36
38
|
key: "GUILD_ID",
|
|
@@ -54,10 +56,23 @@ describe("parseServerkJson", () => {
|
|
|
54
56
|
start: null,
|
|
55
57
|
install: null,
|
|
56
58
|
build: null,
|
|
59
|
+
preDeploy: null,
|
|
57
60
|
env: [],
|
|
58
61
|
});
|
|
59
62
|
});
|
|
60
63
|
|
|
64
|
+
it("reads a file written before the pre-deploy field existed, with no preDeploy", () => {
|
|
65
|
+
expect(
|
|
66
|
+
parseServerkJson(
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
start: "node index.js",
|
|
69
|
+
install: "npm ci",
|
|
70
|
+
build: "npm run build",
|
|
71
|
+
}),
|
|
72
|
+
)?.preDeploy,
|
|
73
|
+
).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
|
|
61
76
|
it("refuses a file with any field it cannot trust", () => {
|
|
62
77
|
for (const text of [
|
|
63
78
|
"[]",
|
|
@@ -68,6 +83,12 @@ describe("parseServerkJson", () => {
|
|
|
68
83
|
'{"start":"a\\nb"}',
|
|
69
84
|
'{"install":""}',
|
|
70
85
|
'{"install":["npm","ci"]}',
|
|
86
|
+
'{"preDeploy":""}',
|
|
87
|
+
'{"preDeploy":" "}',
|
|
88
|
+
'{"preDeploy":"a\\nb"}',
|
|
89
|
+
'{"preDeploy":["prisma","migrate","deploy"]}',
|
|
90
|
+
'{"preDeploy":true}',
|
|
91
|
+
`{"preDeploy":"${"x".repeat(1025)}"}`,
|
|
71
92
|
`{"start":"${"x".repeat(1025)}"}`,
|
|
72
93
|
'{"env":["1BAD"]}',
|
|
73
94
|
'{"env":[{"key":"A","sensitive":"yes"}]}',
|
|
@@ -18,6 +18,7 @@ export interface ServerkJson {
|
|
|
18
18
|
start: string | null;
|
|
19
19
|
install: string | null;
|
|
20
20
|
build: string | null;
|
|
21
|
+
preDeploy: string | null;
|
|
21
22
|
env: ServerkJsonEnv[];
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -159,9 +160,19 @@ export const parseServerkJson = (text: string): ServerkJson | null => {
|
|
|
159
160
|
const start = commandOf(document.start);
|
|
160
161
|
const install = commandOf(document.install);
|
|
161
162
|
const build = commandOf(document.build);
|
|
163
|
+
const preDeploy = commandOf(document.preDeploy);
|
|
162
164
|
const env = envOf(document.env);
|
|
163
165
|
|
|
164
|
-
if (
|
|
166
|
+
if (
|
|
167
|
+
!runtime.ok
|
|
168
|
+
|| !platform.ok
|
|
169
|
+
|| !framework.ok
|
|
170
|
+
|| !start.ok
|
|
171
|
+
|| !install.ok
|
|
172
|
+
|| !build.ok
|
|
173
|
+
|| !preDeploy.ok
|
|
174
|
+
|| !env.ok
|
|
175
|
+
) {
|
|
165
176
|
return null;
|
|
166
177
|
}
|
|
167
178
|
|
|
@@ -172,6 +183,7 @@ export const parseServerkJson = (text: string): ServerkJson | null => {
|
|
|
172
183
|
start: start.value,
|
|
173
184
|
install: install.value,
|
|
174
185
|
build: build.value,
|
|
186
|
+
preDeploy: preDeploy.value,
|
|
175
187
|
env: env.value,
|
|
176
188
|
};
|
|
177
189
|
};
|
package/src/bot/index.ts
CHANGED
package/src/cli/link.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { parseArgs } from "node:util";
|
|
|
4
4
|
|
|
5
5
|
const PACKAGE_NAME = "@serverkgg/bridge";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
export const LINK_SOURCE = "../serverk/modules/bridge";
|
|
8
8
|
|
|
9
9
|
const INSTALL_PATH = `node_modules/${PACKAGE_NAME}`;
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ point ${INSTALL_PATH} at a local bridge checkout without touching package.json o
|
|
|
14
14
|
does nothing when CI is set or the checkout is absent, so the registry package is used everywhere else.
|
|
15
15
|
|
|
16
16
|
options:
|
|
17
|
-
--source <dir> local bridge checkout (default: ${
|
|
17
|
+
--source <dir> local bridge checkout (default: ${LINK_SOURCE})
|
|
18
18
|
--check print which bridge is installed, change nothing
|
|
19
19
|
--unlink drop the link so the next install restores the registry package
|
|
20
20
|
--help show this help`;
|
|
@@ -268,7 +268,7 @@ export const runLink = async (args: string[]) => {
|
|
|
268
268
|
options: {
|
|
269
269
|
source: {
|
|
270
270
|
type: "string",
|
|
271
|
-
default:
|
|
271
|
+
default: LINK_SOURCE,
|
|
272
272
|
},
|
|
273
273
|
check: {
|
|
274
274
|
type: "boolean",
|
package/src/cli/main.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { errorText } from "../runtime/bridgeLog";
|
|
|
4
4
|
import { runCompile } from "./compile";
|
|
5
5
|
import { runDetectTable } from "./detectTable";
|
|
6
6
|
import { runLink } from "./link";
|
|
7
|
+
import { runReadme } from "./readme";
|
|
7
8
|
import { runRules } from "./rules";
|
|
8
9
|
import { runSchema } from "./schema";
|
|
9
10
|
import { runValidate } from "./validate";
|
|
@@ -17,6 +18,7 @@ commands:
|
|
|
17
18
|
compile compile the bridge binary for amd64
|
|
18
19
|
link use a local bridge checkout while developing
|
|
19
20
|
rules write the conventions for package code into .claude/rules
|
|
21
|
+
readme write README.md from serverk.yml
|
|
20
22
|
|
|
21
23
|
run "serverk-bridge <command> --help" for options`;
|
|
22
24
|
|
|
@@ -60,6 +62,12 @@ try {
|
|
|
60
62
|
break;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
case "readme": {
|
|
66
|
+
await runReadme(args);
|
|
67
|
+
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
case undefined:
|
|
64
72
|
case "--help": {
|
|
65
73
|
console.log(USAGE);
|