@serverkgg/bridge 0.1.11 → 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.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/rules/package.md +2 -1
  3. package/src/bot/botDeploy.test.ts +57 -0
  4. package/src/bot/botEnvironment.test.ts +21 -0
  5. package/src/bot/botEnvironment.ts +10 -0
  6. package/src/bot/botRun.test.ts +296 -0
  7. package/src/bot/botRun.ts +109 -0
  8. package/src/bot/botState.test.ts +34 -1
  9. package/src/bot/botState.ts +1 -0
  10. package/src/bot/botVariables.test.ts +22 -0
  11. package/src/bot/botVariables.ts +3 -0
  12. package/src/bot/detect/detect.test.ts +172 -0
  13. package/src/bot/detect/detect.ts +31 -0
  14. package/src/bot/detect/detectResult.ts +1 -0
  15. package/src/bot/detect/detectTable.test.ts +25 -2
  16. package/src/bot/detect/detectTable.ts +24 -1
  17. package/src/bot/detect/keys.test.ts +7 -0
  18. package/src/bot/detect/keys.ts +3 -0
  19. package/src/bot/detect/serverkJson.test.ts +21 -0
  20. package/src/bot/detect/serverkJson.ts +13 -1
  21. package/src/bot/index.ts +1 -0
  22. package/src/cli/link.ts +3 -3
  23. package/src/cli/main.ts +8 -0
  24. package/src/cli/readme.test.ts +470 -0
  25. package/src/cli/readme.ts +570 -0
  26. package/src/cli/validate.test.ts +41 -1
  27. package/src/cli/validate.ts +25 -0
  28. package/src/context/bridgeContext.ts +1 -0
  29. package/src/modules/bridgeModule.ts +1 -0
  30. package/src/protocol/bridgeManifest.ts +1 -0
  31. package/src/runtime/bridgeContext.test.ts +6 -0
  32. package/src/runtime/bridgeContext.ts +7 -2
  33. package/src/runtime/bridgeExec.test.ts +150 -0
  34. package/src/runtime/bridgeExec.ts +67 -19
  35. package/src/runtime/bridgeLineSpacing.test.ts +132 -0
  36. package/src/runtime/bridgeLineSpacing.ts +49 -0
  37. package/src/runtime/bridgeLog.ts +9 -0
  38. package/src/runtime/bridgeRedaction.test.ts +28 -2
  39. package/src/runtime/bridgeRedaction.ts +7 -1
  40. package/src/runtime/bridgeRegistry.test.ts +30 -0
  41. package/src/runtime/bridgeRegistry.ts +1 -0
  42. package/src/runtime/bridgeScopedContext.ts +3 -0
  43. package/src/runtime/bridgeSetupHandlers.test.ts +3 -0
  44. package/src/runtime/bridgeSupervisor.ts +7 -9
  45. package/src/runtime/serve.ts +9 -2
  46. package/src/steam/steamcmdNarrator.test.ts +18 -5
  47. package/src/steam/steamcmdNarrator.ts +26 -2
  48. package/src/wine/protonCommand.test.ts +17 -1
  49. package/src/wine/protonCommand.ts +7 -1
  50. package/src/wire/panel.test.ts +32 -0
  51. 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",
@@ -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(
@@ -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(rendered).toContain('\t"version": 2,');
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 = 2;
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
  });
@@ -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 (!runtime.ok || !platform.ok || !framework.ok || !start.ok || !install.ok || !build.ok || !env.ok) {
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
@@ -3,6 +3,7 @@ export * from "./botDeploy";
3
3
  export * from "./botEnvironment";
4
4
  export * from "./botIdentity";
5
5
  export * from "./botPanel";
6
+ export * from "./botRun";
6
7
  export * from "./botState";
7
8
  export * from "./botTerminal";
8
9
  export * from "./botVariables";
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 DEFAULT_SOURCE = "../serverk/modules/bridge";
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: ${DEFAULT_SOURCE})
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: DEFAULT_SOURCE,
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);