@serverkgg/bridge 0.0.1 → 0.0.2

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 (59) hide show
  1. package/package.json +10 -3
  2. package/src/cli/main.ts +11 -1
  3. package/src/cli/schema.ts +62 -0
  4. package/src/cli/validate.ts +119 -36
  5. package/src/context/bridgeContext.ts +11 -1
  6. package/src/core/bridgeAction.ts +13 -0
  7. package/src/core/bridgeFailure.ts +16 -0
  8. package/src/core/bridgeKind.ts +2 -0
  9. package/src/core/bridgeNetError.ts +10 -0
  10. package/src/core/index.ts +2 -0
  11. package/src/guides/blocks.ts +167 -0
  12. package/src/guides/compile.ts +193 -0
  13. package/src/guides/compiled.ts +28 -0
  14. package/src/guides/index.ts +6 -0
  15. package/src/guides/markdown.test.ts +374 -0
  16. package/src/guides/markdown.ts +769 -0
  17. package/src/guides/variables.ts +52 -0
  18. package/src/guides/version.ts +1 -0
  19. package/src/main.ts +2 -1
  20. package/src/manifest/assets.ts +4 -4
  21. package/src/manifest/guide.ts +23 -18
  22. package/src/manifest/index.ts +3 -2
  23. package/src/manifest/jsonSchema.ts +16 -0
  24. package/src/manifest/localized.ts +7 -0
  25. package/src/manifest/manifest.test.ts +366 -0
  26. package/src/manifest/manifest.ts +354 -120
  27. package/src/manifest/primitives.ts +63 -0
  28. package/src/manifest/source.ts +5 -4
  29. package/src/modules/bridge.ts +19 -3
  30. package/src/modules/bridgeDriver.ts +4 -0
  31. package/src/modules/bridgeModule.ts +22 -24
  32. package/src/panel/bridgeAccess.ts +3 -0
  33. package/src/panel/bridgePanel.ts +6 -0
  34. package/src/panel/index.ts +1 -0
  35. package/src/protocol/bridgeCatalog.ts +56 -0
  36. package/src/protocol/bridgeEgress.ts +53 -0
  37. package/src/protocol/bridgeFrame.ts +17 -1
  38. package/src/protocol/bridgeLine.ts +41 -6
  39. package/src/protocol/bridgeManifest.ts +15 -1
  40. package/src/protocol/bridgeVersion.ts +1 -1
  41. package/src/protocol/index.ts +3 -0
  42. package/src/runtime/bridgeContext.ts +29 -9
  43. package/src/runtime/bridgeDriver.ts +1 -0
  44. package/src/runtime/bridgeEgress.ts +360 -0
  45. package/src/runtime/bridgeExec.ts +15 -5
  46. package/src/runtime/bridgeFiles.ts +58 -21
  47. package/src/runtime/bridgeLog.ts +26 -3
  48. package/src/runtime/bridgeNet.ts +75 -22
  49. package/src/runtime/bridgeRegistry.ts +51 -6
  50. package/src/runtime/bridgeServer.ts +48 -1
  51. package/src/runtime/bridgeSupervisor.ts +6 -6
  52. package/src/runtime/index.ts +0 -17
  53. package/src/runtime/serve.ts +44 -3
  54. package/src/terminal/bridgeTerminalArg.ts +11 -0
  55. package/src/terminal/bridgeTerminalCommand.ts +11 -0
  56. package/src/terminal/bridgeTerminalLevel.ts +6 -0
  57. package/src/terminal/bridgeTerminalRule.ts +8 -0
  58. package/src/terminal/index.ts +4 -0
  59. package/src/manifest/checks.ts +0 -50
@@ -1,7 +1,12 @@
1
1
  import type { BridgeContext } from "../context";
2
2
  import { BridgeAction, BridgeKind, type BridgeRow, type BridgeValues } from "../core";
3
- import type { BridgeDriver, BridgePanelModule } from "../modules";
4
- import { BRIDGE_VERSION, type BridgeManifest, type BridgeModuleManifest } from "../protocol";
3
+ import type { BridgeDriver, BridgePanelModule, BridgeTerminalModule } from "../modules";
4
+ import {
5
+ BRIDGE_VERSION,
6
+ type BridgeManifest,
7
+ type BridgeModuleManifest,
8
+ type BridgeTerminalManifest,
9
+ } from "../protocol";
5
10
  import { BridgeDriverError } from "./bridgeDriver";
6
11
  import type { BridgeEventRing } from "./bridgeEvents";
7
12
  import type { BridgeSupervisor } from "./bridgeSupervisor";
@@ -48,6 +53,11 @@ const actionsOf = (module: BridgePanelModule) => {
48
53
  BridgeAction.Installed,
49
54
  BridgeAction.Remove,
50
55
  BridgeAction.Search,
56
+ ...(module.toggle
57
+ ? [
58
+ BridgeAction.Toggle,
59
+ ]
60
+ : []),
51
61
  ];
52
62
  }
53
63
  }
@@ -69,6 +79,24 @@ const pageSizeOf = (module: BridgePanelModule) => {
69
79
  return module.kind === BridgeKind.Catalog ? (module.pageSize ?? null) : null;
70
80
  };
71
81
 
82
+ const terminalManifestOf = (module: BridgeTerminalModule | undefined): BridgeTerminalManifest | null => {
83
+ if (!module) {
84
+ return null;
85
+ }
86
+
87
+ return {
88
+ commands: module.commands,
89
+ rules: (module.rules ?? []).map((rule) => {
90
+ return {
91
+ match: rule.match.source,
92
+ flags: rule.match.flags,
93
+ level: rule.level,
94
+ stream: rule.stream,
95
+ };
96
+ }),
97
+ };
98
+ };
99
+
72
100
  const withVariables = (context: BridgeContext, overlay: Record<string, string>): BridgeContext => {
73
101
  return {
74
102
  get server() {
@@ -97,9 +125,7 @@ const withVariables = (context: BridgeContext, overlay: Record<string, string>):
97
125
  emit(event, payload) {
98
126
  context.emit(event, payload);
99
127
  },
100
- log(message, fields) {
101
- context.log(message, fields);
102
- },
128
+ log: context.log,
103
129
  };
104
130
  };
105
131
 
@@ -119,6 +145,12 @@ const asText = (args: BridgeValues, key: string) => {
119
145
  return typeof value === "string" ? value : "";
120
146
  };
121
147
 
148
+ const asOptional = (args: BridgeValues, key: string) => {
149
+ const value = args[key];
150
+
151
+ return typeof value === "string" && value.length > 0 ? value : null;
152
+ };
153
+
122
154
  const asPage = (args: BridgeValues) => {
123
155
  const value = args.page;
124
156
 
@@ -246,7 +278,13 @@ export const createRegistry = (
246
278
 
247
279
  case BridgeKind.Catalog: {
248
280
  if (action === BridgeAction.Search) {
249
- return await module.search(context, asText(args, "query"), asPage(args));
281
+ return await module.search(context, {
282
+ query: asText(args, "query"),
283
+ page: asPage(args),
284
+ provider: asOptional(args, "provider"),
285
+ category: asOptional(args, "category"),
286
+ sort: asOptional(args, "sort"),
287
+ });
250
288
  }
251
289
 
252
290
  if (action === BridgeAction.Installed) {
@@ -263,6 +301,12 @@ export const createRegistry = (
263
301
  return null;
264
302
  }
265
303
 
304
+ if (action === BridgeAction.Toggle && module.toggle) {
305
+ await module.toggle(context, asString(args, "id"), args.enabled !== false);
306
+
307
+ return null;
308
+ }
309
+
266
310
  break;
267
311
  }
268
312
  }
@@ -287,6 +331,7 @@ export const createRegistry = (
287
331
  return {
288
332
  version: BRIDGE_VERSION,
289
333
  panel: driver.panel ?? null,
334
+ terminal: terminalManifestOf(driver.terminal),
290
335
  modules: entries,
291
336
  events: ring.names(),
292
337
  };
@@ -1,8 +1,10 @@
1
1
  import { timingSafeEqual } from "node:crypto";
2
- import { BridgeSecretError, type BridgeValues } from "../core";
2
+ import { BridgeFailureError, BridgeSecretError, type BridgeValues, type LocalizedText } from "../core";
3
3
  import {
4
4
  BRIDGE_FRAME_LIMIT,
5
+ type BridgeAnnounceResult,
5
6
  type BridgeCommandResult,
7
+ type BridgeDescribeResult,
6
8
  type BridgeEventsResult,
7
9
  type BridgeManifest,
8
10
  BridgeMethod,
@@ -26,6 +28,8 @@ export interface ServerOptions {
26
28
  onSample: () => Promise<BridgeSampleResult>;
27
29
  onQuiesce: () => Promise<BridgeQuiesceResult>;
28
30
  onRelease: () => Promise<void>;
31
+ onDescribe: () => Promise<BridgeDescribeResult>;
32
+ onAnnounce: (message: LocalizedText) => Promise<BridgeAnnounceResult>;
29
33
  onLog: BridgeLogger;
30
34
  }
31
35
 
@@ -40,8 +44,26 @@ interface IncomingFrame {
40
44
  action?: unknown;
41
45
  args?: unknown;
42
46
  cursor?: unknown;
47
+ message?: unknown;
43
48
  }
44
49
 
50
+ const asMessage = (value: unknown): LocalizedText | null => {
51
+ if (value === null || typeof value !== "object") {
52
+ return null;
53
+ }
54
+
55
+ const { ar, en } = value as Record<string, unknown>;
56
+
57
+ if (typeof ar !== "string" || typeof en !== "string") {
58
+ return null;
59
+ }
60
+
61
+ return {
62
+ ar,
63
+ en,
64
+ };
65
+ };
66
+
45
67
  const matches = (left: string, right: string) => {
46
68
  const a = Buffer.from(left);
47
69
  const b = Buffer.from(right);
@@ -122,6 +144,20 @@ export const createServer = (options: ServerOptions) => {
122
144
  return null;
123
145
  }
124
146
 
147
+ case BridgeMethod.Describe: {
148
+ return await options.onDescribe();
149
+ }
150
+
151
+ case BridgeMethod.Announce: {
152
+ const message = asMessage(request.message);
153
+
154
+ if (!message) {
155
+ throw new Error("announce needs a message with ar and en text");
156
+ }
157
+
158
+ return await options.onAnnounce(message);
159
+ }
160
+
125
161
  default: {
126
162
  throw new Error(`unknown method "${method}"`);
127
163
  }
@@ -205,6 +241,12 @@ export const createServer = (options: ServerOptions) => {
205
241
  );
206
242
  })
207
243
  .catch((error: unknown) => {
244
+ options.onLog("bridge call failed", {
245
+ method: String(request.method ?? ""),
246
+ error: errorText(error),
247
+ stack: error instanceof Error ? (error.stack ?? null) : null,
248
+ });
249
+
208
250
  socket.write(
209
251
  encode({
210
252
  id,
@@ -215,6 +257,11 @@ export const createServer = (options: ServerOptions) => {
215
257
  secret: error.key,
216
258
  }
217
259
  : {}),
260
+ ...(error instanceof BridgeFailureError
261
+ ? {
262
+ failure: error.code,
263
+ }
264
+ : {}),
218
265
  }),
219
266
  );
220
267
  });
@@ -1,6 +1,6 @@
1
1
  import type { BridgeCommandOptions, BridgeReply } from "../context";
2
2
  import { BridgeRuntime, BridgeStream } from "../core";
3
- import type { BridgeLogger } from "./bridgeLog";
3
+ import type { BridgeLeveledLogger } from "./bridgeLog";
4
4
  import { errorText } from "./bridgeLog";
5
5
  import { settleUid, unprivilegedArgv } from "./bridgePrivilege";
6
6
 
@@ -32,7 +32,7 @@ export interface SupervisorOptions {
32
32
  resolveCommand: () => Promise<string[]>;
33
33
  gracefulStop?: () => Promise<void>;
34
34
  onReady?: () => void;
35
- onLog: BridgeLogger;
35
+ onLog: BridgeLeveledLogger;
36
36
  }
37
37
 
38
38
  type LineWatcher = (entry: SupervisorLine) => void;
@@ -295,7 +295,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
295
295
  } catch (error) {
296
296
  runtime = BridgeRuntime.Crashed;
297
297
 
298
- options.onLog("game failed to restart", {
298
+ options.onLog.error("game failed to restart", {
299
299
  error: errorText(error),
300
300
  });
301
301
  }
@@ -322,7 +322,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
322
322
  breaker = true;
323
323
  runtime = BridgeRuntime.Crashed;
324
324
 
325
- options.onLog("game crash loop detected, not restarting", {
325
+ options.onLog.error("game crash loop detected, not restarting", {
326
326
  code,
327
327
  restarts,
328
328
  });
@@ -332,7 +332,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
332
332
 
333
333
  runtime = BridgeRuntime.Starting;
334
334
 
335
- options.onLog("game exited, restarting", {
335
+ options.onLog.warn("game exited, restarting", {
336
336
  code,
337
337
  restarts,
338
338
  });
@@ -405,7 +405,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
405
405
 
406
406
  if (options.gracefulStop) {
407
407
  const graceful = options.gracefulStop().catch((error: unknown) => {
408
- options.onLog("graceful stop failed", {
408
+ options.onLog.warn("graceful stop failed", {
409
409
  error: errorText(error),
410
410
  });
411
411
  });
@@ -1,18 +1 @@
1
- export * from "./bridgeCodec";
2
- export * from "./bridgeConfigReader";
3
- export * from "./bridgeContext";
4
- export * from "./bridgeDriver";
5
- export * from "./bridgeEvents";
6
- export * from "./bridgeExec";
7
- export * from "./bridgeFiles";
8
- export * from "./bridgeLog";
9
- export * from "./bridgeNet";
10
- export * from "./bridgePath";
11
- export * from "./bridgePrivilege";
12
- export * from "./bridgeProbe";
13
- export * from "./bridgeRegistry";
14
- export * from "./bridgeServer";
15
- export * from "./bridgeState";
16
- export * from "./bridgeSupervisor";
17
- export * from "./bridgeZip";
18
1
  export * from "./serve";
@@ -79,7 +79,7 @@ export const serve = async (driver: BridgeDriver) => {
79
79
  },
80
80
  onReady() {
81
81
  void Promise.resolve(driver.lifecycle.onReady?.(context)).catch((error: unknown) => {
82
- log("lifecycle ready hook failed", {
82
+ log.warn("lifecycle ready hook failed", {
83
83
  error: errorText(error),
84
84
  });
85
85
  });
@@ -219,6 +219,47 @@ export const serve = async (driver: BridgeDriver) => {
219
219
  await driver.backup?.release?.(context);
220
220
  }
221
221
  },
222
+ async onDescribe() {
223
+ const describe = driver.install.describe;
224
+
225
+ if (!describe) {
226
+ return {
227
+ supported: false,
228
+ version: null,
229
+ variant: null,
230
+ build: null,
231
+ };
232
+ }
233
+
234
+ return {
235
+ supported: true,
236
+ ...(await describe(context)),
237
+ };
238
+ },
239
+ async onAnnounce(message) {
240
+ const announce = driver.announce;
241
+
242
+ if (!announce) {
243
+ return {
244
+ supported: false,
245
+ delivered: false,
246
+ };
247
+ }
248
+
249
+ if (!supervisor.running) {
250
+ return {
251
+ supported: true,
252
+ delivered: false,
253
+ };
254
+ }
255
+
256
+ await announce.announce(context, message);
257
+
258
+ return {
259
+ supported: true,
260
+ delivered: true,
261
+ };
262
+ },
222
263
  });
223
264
 
224
265
  await chmod(socketPath(config), SOCKET_MODE);
@@ -271,7 +312,7 @@ export const serve = async (driver: BridgeDriver) => {
271
312
 
272
313
  supervisor.setRuntime(BridgeRuntime.Crashed);
273
314
 
274
- log("install failed", {
315
+ log.error("install failed", {
275
316
  error: installError,
276
317
  });
277
318
 
@@ -286,7 +327,7 @@ export const serve = async (driver: BridgeDriver) => {
286
327
  } catch (error) {
287
328
  supervisor.setRuntime(BridgeRuntime.Crashed);
288
329
 
289
- log("start failed", {
330
+ log.error("start failed", {
290
331
  error: errorText(error),
291
332
  });
292
333
  }
@@ -0,0 +1,11 @@
1
+ import type { LocalizedText } from "../core";
2
+
3
+ export interface BridgeTerminalArg {
4
+ key: string;
5
+ label?: LocalizedText;
6
+ required?: boolean;
7
+ variadic?: boolean;
8
+ values?: string[];
9
+ module?: string;
10
+ column?: string;
11
+ }
@@ -0,0 +1,11 @@
1
+ import type { LocalizedText } from "../core";
2
+ import type { BridgeTerminalArg } from "./bridgeTerminalArg";
3
+
4
+ export interface BridgeTerminalCommand {
5
+ name: string;
6
+ summary: LocalizedText;
7
+ syntax?: string;
8
+ args?: BridgeTerminalArg[];
9
+ danger?: boolean;
10
+ requiresRunning?: boolean;
11
+ }
@@ -0,0 +1,6 @@
1
+ export enum BridgeTerminalLevel {
2
+ Debug = "debug",
3
+ Info = "info",
4
+ Warn = "warn",
5
+ Error = "error",
6
+ }
@@ -0,0 +1,8 @@
1
+ import type { BridgeStream } from "../core";
2
+ import type { BridgeTerminalLevel } from "./bridgeTerminalLevel";
3
+
4
+ export interface BridgeTerminalRule {
5
+ match: RegExp;
6
+ level: BridgeTerminalLevel;
7
+ stream?: BridgeStream;
8
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./bridgeTerminalArg";
2
+ export * from "./bridgeTerminalCommand";
3
+ export * from "./bridgeTerminalLevel";
4
+ export * from "./bridgeTerminalRule";
@@ -1,50 +0,0 @@
1
- import type { Manifest, ManifestPort } from "./manifest";
2
-
3
- const overlaps = (a: ManifestPort, b: ManifestPort) => {
4
- return a.host.min <= b.host.max && b.host.min <= a.host.max;
5
- };
6
-
7
- export const checkManifest = (manifest: Manifest) => {
8
- const portKeys = manifest.runtime.ports.map((port) => port.key);
9
-
10
- if (new Set(portKeys).size !== portKeys.length) {
11
- throw new Error("runtime declares duplicate port keys");
12
- }
13
-
14
- const ports = manifest.runtime.ports;
15
-
16
- for (let index = 0; index < ports.length; index += 1) {
17
- for (let other = index + 1; other < ports.length; other += 1) {
18
- const a = ports[index];
19
- const b = ports[other];
20
-
21
- if (!a || !b || a.protocol !== b.protocol) {
22
- continue;
23
- }
24
-
25
- if (overlaps(a, b)) {
26
- throw new Error(`ports "${a.key}" and "${b.key}" declare overlapping ${a.protocol} host ranges`);
27
- }
28
- }
29
- }
30
-
31
- const guideIds = manifest.guides.map((guide) => guide.id);
32
-
33
- if (new Set(guideIds).size !== guideIds.length) {
34
- throw new Error("guides declare duplicate ids");
35
- }
36
-
37
- const bodies: string[] = [];
38
-
39
- for (const guide of manifest.guides) {
40
- if (guide.body.ar === guide.body.en) {
41
- throw new Error(`guide "${guide.id}" reuses one body file for both locales`);
42
- }
43
-
44
- bodies.push(guide.body.ar, guide.body.en);
45
- }
46
-
47
- if (new Set(bodies).size !== bodies.length) {
48
- throw new Error("guides declare duplicate body paths");
49
- }
50
- };