@serverkgg/bridge 0.0.21 → 0.1.1

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 (86) hide show
  1. package/package.json +3 -1
  2. package/src/bot/botDeploy.test.ts +363 -0
  3. package/src/bot/botDeploy.ts +136 -0
  4. package/src/bot/botEnvironment.test.ts +189 -0
  5. package/src/bot/botEnvironment.ts +141 -0
  6. package/src/bot/botIdentity.test.ts +251 -0
  7. package/src/bot/botIdentity.ts +140 -0
  8. package/src/bot/botPanel.test.ts +216 -0
  9. package/src/bot/botPanel.ts +121 -0
  10. package/src/bot/botState.test.ts +45 -0
  11. package/src/bot/botState.ts +54 -0
  12. package/src/bot/botTerminal.test.ts +79 -0
  13. package/src/bot/botTerminal.ts +62 -0
  14. package/src/bot/botVariables.test.ts +193 -0
  15. package/src/bot/botVariables.ts +73 -0
  16. package/src/bot/detect/botFramework.ts +23 -0
  17. package/src/bot/detect/botFrameworks.ts +198 -0
  18. package/src/bot/detect/botInspectWarning.ts +15 -0
  19. package/src/bot/detect/botPackageManager.ts +6 -0
  20. package/src/bot/detect/botPlatform.ts +10 -0
  21. package/src/bot/detect/botRuntime.ts +15 -0
  22. package/src/bot/detect/botToken.test.ts +71 -0
  23. package/src/bot/detect/botToken.ts +126 -0
  24. package/src/bot/detect/detect.test.ts +543 -0
  25. package/src/bot/detect/detect.ts +842 -0
  26. package/src/bot/detect/detectResult.ts +40 -0
  27. package/src/bot/detect/detectTable.test.ts +72 -0
  28. package/src/bot/detect/detectTable.ts +166 -0
  29. package/src/bot/detect/envExample.test.ts +29 -0
  30. package/src/bot/detect/envExample.ts +48 -0
  31. package/src/bot/detect/envKey.ts +7 -0
  32. package/src/bot/detect/envScan.test.ts +39 -0
  33. package/src/bot/detect/envScan.ts +87 -0
  34. package/src/bot/detect/index.ts +17 -0
  35. package/src/bot/detect/inferBotPlatform.test.ts +60 -0
  36. package/src/bot/detect/inferBotPlatform.ts +28 -0
  37. package/src/bot/detect/keys.test.ts +28 -0
  38. package/src/bot/detect/keys.ts +64 -0
  39. package/src/bot/detect/sensitiveKey.test.ts +91 -0
  40. package/src/bot/detect/sensitiveKey.ts +44 -0
  41. package/src/bot/detect/serverkJson.test.ts +88 -0
  42. package/src/bot/detect/serverkJson.ts +174 -0
  43. package/src/bot/index.ts +8 -0
  44. package/src/cli/detectTable.test.ts +66 -0
  45. package/src/cli/detectTable.ts +43 -0
  46. package/src/cli/main.ts +12 -4
  47. package/src/cli/renderedFile.ts +35 -0
  48. package/src/cli/schema.ts +7 -26
  49. package/src/cli/validate.test.ts +210 -2
  50. package/src/cli/validate.ts +71 -7
  51. package/src/context/bridgeContext.ts +11 -0
  52. package/src/core/bridgePort.test.ts +45 -0
  53. package/src/core/bridgePort.ts +26 -0
  54. package/src/guides/compile.ts +2 -2
  55. package/src/manifest/index.ts +1 -0
  56. package/src/manifest/kind.ts +5 -0
  57. package/src/manifest/manifest.test.ts +630 -3
  58. package/src/manifest/manifest.ts +391 -87
  59. package/src/manifest/runtime.ts +2 -2
  60. package/src/manifest/source.ts +4 -4
  61. package/src/modules/bridgeLifecycleSubject.ts +8 -0
  62. package/src/modules/bridgeModule.ts +6 -0
  63. package/src/modules/bridgeStopSignal.ts +13 -0
  64. package/src/modules/index.ts +2 -0
  65. package/src/panel/bridgeDetail.ts +2 -0
  66. package/src/protocol/bridgeConfig.ts +2 -0
  67. package/src/runtime/bridgeConfigReader.test.ts +80 -0
  68. package/src/runtime/bridgeConfigReader.ts +20 -0
  69. package/src/runtime/bridgeContext.test.ts +241 -0
  70. package/src/runtime/bridgeContext.ts +35 -9
  71. package/src/runtime/bridgeDriver.test.ts +132 -1
  72. package/src/runtime/bridgeDriver.ts +54 -1
  73. package/src/runtime/bridgeFiles.test.ts +62 -0
  74. package/src/runtime/bridgeFiles.ts +12 -2
  75. package/src/runtime/bridgeLiveConfig.ts +18 -0
  76. package/src/runtime/bridgeRedaction.test.ts +410 -0
  77. package/src/runtime/bridgeRedaction.ts +214 -0
  78. package/src/runtime/bridgeRegistry.test.ts +3 -0
  79. package/src/runtime/bridgeRegistry.ts +15 -0
  80. package/src/runtime/bridgeServer.test.ts +130 -0
  81. package/src/runtime/bridgeServer.ts +24 -7
  82. package/src/runtime/bridgeSetupHandlers.test.ts +88 -0
  83. package/src/runtime/bridgeSetupHandlers.ts +16 -6
  84. package/src/runtime/bridgeSupervisor.test.ts +670 -2
  85. package/src/runtime/bridgeSupervisor.ts +219 -67
  86. package/src/runtime/serve.ts +47 -13
@@ -1,9 +1,11 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
1
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { renderDetectTable } from "../bot/detect";
5
6
  import { BridgeKind, BridgeSetupStepKind } from "../core";
6
7
  import { GuideOpenTab } from "../guides";
8
+ import { MANIFEST_SCHEMA_VERSION, PackageKind } from "../manifest";
7
9
  import type { BridgeDriver, BridgeSetupModule } from "../modules";
8
10
  import {
9
11
  BridgeControl,
@@ -394,3 +396,209 @@ describe("the placement report", () => {
394
396
  ).toEqual([]);
395
397
  });
396
398
  });
399
+
400
+ const webp = (width: number, height: number) => {
401
+ const bytes = new Uint8Array(30);
402
+ const view = new DataView(bytes.buffer);
403
+ const ascii = (offset: number, value: string) => {
404
+ for (let index = 0; index < value.length; index += 1) {
405
+ bytes[offset + index] = value.charCodeAt(index);
406
+ }
407
+ };
408
+
409
+ ascii(0, "RIFF");
410
+ view.setUint32(4, bytes.byteLength - 8, true);
411
+ ascii(8, "WEBP");
412
+ ascii(12, "VP8X");
413
+ view.setUint32(16, 10, true);
414
+ view.setUint16(24, (width - 1) & 0xff_ff, true);
415
+ bytes[26] = ((width - 1) >> 16) & 0xff;
416
+ view.setUint16(27, (height - 1) & 0xff_ff, true);
417
+ bytes[29] = ((height - 1) >> 16) & 0xff;
418
+
419
+ return bytes;
420
+ };
421
+
422
+ const BOT_DRIVER = `export const driver = {
423
+ install: {
424
+ kind: "install",
425
+ run: async () => undefined,
426
+ },
427
+ lifecycle: {
428
+ kind: "lifecycle",
429
+ command: async () => ["node", "/serverk/launcher.mjs"],
430
+ environment: async () => ({}),
431
+ },
432
+ };
433
+ `;
434
+
435
+ const botManifest = (extra: Record<string, unknown> = {}) => ({
436
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
437
+ kind: PackageKind.Bot,
438
+ metadata: {
439
+ id: "bot-node",
440
+ name: text("Node.js Bot"),
441
+ description: text("Run your own bot"),
442
+ tagline: text("Your code, always on"),
443
+ logo: "assets/logo.webp",
444
+ banner: "assets/banner.webp",
445
+ },
446
+ resources: {
447
+ minMemoryMb: 512,
448
+ recommendedMemoryMb: 1024,
449
+ minDiskMb: 2048,
450
+ },
451
+ container: {
452
+ image: "serverkgg/bot-node",
453
+ ports: [],
454
+ },
455
+ ...extra,
456
+ });
457
+
458
+ const WEBHOOK_PORT = {
459
+ key: "webhook",
460
+ containerPort: 8080,
461
+ protocol: "tcp",
462
+ hostRange: {
463
+ default: 30_000,
464
+ min: 30_000,
465
+ max: 30_099,
466
+ },
467
+ };
468
+
469
+ describe("validating a bot package", () => {
470
+ let root = "";
471
+ let lines: string[] = [];
472
+ let restore = () => {};
473
+
474
+ const writeBot = (manifest: Record<string, unknown>, detectTable: string | null = renderDetectTable()) => {
475
+ mkdirSync(join(root, "assets"));
476
+ mkdirSync(join(root, "src"));
477
+ mkdirSync(join(root, "image"));
478
+
479
+ if (detectTable !== null) {
480
+ writeFileSync(join(root, "image/detect.json"), detectTable);
481
+ }
482
+
483
+ writeFileSync(
484
+ join(root, "package.json"),
485
+ JSON.stringify({
486
+ name: "@serverkgg/bot-node",
487
+ dependencies: {
488
+ "@serverkgg/bridge": "0.1.0",
489
+ },
490
+ }),
491
+ );
492
+ writeFileSync(join(root, "serverk.yml"), JSON.stringify(manifest));
493
+ writeFileSync(join(root, "assets/logo.webp"), webp(256, 256));
494
+ writeFileSync(join(root, "assets/banner.webp"), webp(1280, 720));
495
+ writeFileSync(join(root, "src/driver.ts"), BOT_DRIVER);
496
+ };
497
+
498
+ const validate = () => {
499
+ return runValidate([
500
+ "--dir",
501
+ root,
502
+ ]);
503
+ };
504
+
505
+ beforeEach(() => {
506
+ root = mkdtempSync(`${tmpdir()}/serverk-validate-bot-`);
507
+ lines = [];
508
+
509
+ const spy = spyOn(console, "log").mockImplementation((line: unknown) => {
510
+ lines.push(String(line));
511
+ });
512
+
513
+ restore = () => spy.mockRestore();
514
+ });
515
+
516
+ afterEach(() => {
517
+ restore();
518
+ rmSync(root, {
519
+ force: true,
520
+ recursive: true,
521
+ });
522
+ });
523
+
524
+ test("validates a bot with no ports and says which kind it read", async () => {
525
+ writeBot(botManifest());
526
+
527
+ await validate();
528
+
529
+ expect(lines).toContain("bot-node is valid");
530
+ expect(lines).toContain(
531
+ " manifest: BotPackage, 0 port(s), 0 conditional, 0 tcp+udp, 0 block(s), 0 secret(s), 0 guide(s)",
532
+ );
533
+ expect(lines).toContain(" environment: vault off");
534
+ expect(lines).toContain(" detect: image/detect.json matches this bridge");
535
+ expect(lines.some((line) => line.includes("warning"))).toBe(false);
536
+ });
537
+
538
+ test("says when a bot turns the environment vault on", async () => {
539
+ writeBot(
540
+ botManifest({
541
+ environment: {
542
+ vault: true,
543
+ },
544
+ }),
545
+ );
546
+
547
+ await validate();
548
+
549
+ expect(lines).toContain(" environment: vault on");
550
+ });
551
+
552
+ test("fails a bot whose detection table is missing", async () => {
553
+ writeBot(botManifest(), null);
554
+
555
+ await expect(validate()).rejects.toThrow(
556
+ 'image/detect.json is missing — run "serverk-bridge detect-table" and commit it',
557
+ );
558
+ });
559
+
560
+ test("fails a bot whose detection table is stale", async () => {
561
+ writeBot(botManifest(), "{}\n");
562
+
563
+ await expect(validate()).rejects.toThrow(
564
+ 'image/detect.json is out of date — run "serverk-bridge detect-table" and commit it',
565
+ );
566
+ });
567
+
568
+ test("warns about a port on a bot but still validates it", async () => {
569
+ writeBot(
570
+ botManifest({
571
+ container: {
572
+ image: "serverkgg/bot-node",
573
+ ports: [
574
+ WEBHOOK_PORT,
575
+ ],
576
+ },
577
+ }),
578
+ );
579
+
580
+ await validate();
581
+
582
+ expect(lines).toContain("bot-node is valid");
583
+ expect(lines).toContain(
584
+ " warning: a bot package declares 1 port(s); bots need none — remove them unless a webhook needs a public port",
585
+ );
586
+ });
587
+
588
+ test("fails a bot that declares platform secrets", async () => {
589
+ writeBot(
590
+ botManifest({
591
+ secrets: [
592
+ {
593
+ key: "API_KEY",
594
+ description: text("A key"),
595
+ },
596
+ ],
597
+ }),
598
+ );
599
+
600
+ await expect(validate()).rejects.toThrow(
601
+ "a bot package declares no platform secrets; per-bot values live in the environment vault",
602
+ );
603
+ });
604
+ });
@@ -4,13 +4,18 @@ import { pathToFileURL } from "node:url";
4
4
  import { parseArgs } from "node:util";
5
5
  import { z } from "zod";
6
6
  import { type ArticlePack, compileArticles } from "../blog";
7
+ import { DETECT_TABLE_FILE, renderDetectTable } from "../bot/detect";
8
+ import { PortProtocol } from "../core";
7
9
  import { GuideOpenTab, type GuideOpenTarget, type GuidePack, readGuidePack, readGuidePackage } from "../guides";
8
10
  import {
9
11
  declaredFiles,
10
12
  type GuidePackage,
13
+ isBotManifest,
11
14
  MANIFEST_FILE,
12
- type Manifest,
15
+ type Package,
16
+ PackageKind,
13
17
  type PackageSource,
18
+ type RunnableManifest,
14
19
  readAssets,
15
20
  readPackageDocument,
16
21
  } from "../manifest";
@@ -31,7 +36,7 @@ const HELP = `serverk-bridge validate [options]
31
36
 
32
37
  options:
33
38
  --dir <path> package directory (default: current directory)
34
- --driver <path> driver module (default: <dir>/src/driver.ts), game packages only
39
+ --driver <path> driver module (default: <dir>/src/driver.ts), runnable packages only
35
40
  --help show this help`;
36
41
 
37
42
  const BRIDGE_PACKAGE = "@serverkgg/bridge";
@@ -317,7 +322,54 @@ export const setupReportLines = (setup: BridgeSetupModule | undefined): string[]
317
322
  ];
318
323
  };
319
324
 
320
- const validateGamePackage = async (source: PackageSource, manifest: Manifest, dir: string, driverArg?: string) => {
325
+ export const botPortLines = (manifest: RunnableManifest): string[] => {
326
+ const ports = manifest.container.ports.length;
327
+
328
+ if (!isBotManifest(manifest) || ports === 0) {
329
+ return [];
330
+ }
331
+
332
+ return [
333
+ ` warning: a bot package declares ${ports} port(s); bots need none — remove them unless a webhook needs a public port`,
334
+ ];
335
+ };
336
+
337
+ export const botEnvironmentLines = (manifest: RunnableManifest): string[] => {
338
+ if (!isBotManifest(manifest)) {
339
+ return [];
340
+ }
341
+
342
+ return [
343
+ ` environment: vault ${manifest.environment.vault ? "on" : "off"}`,
344
+ ];
345
+ };
346
+
347
+ export const botDetectLines = (source: PackageSource, manifest: RunnableManifest): string[] => {
348
+ if (!isBotManifest(manifest)) {
349
+ return [];
350
+ }
351
+
352
+ const bytes = source.read(DETECT_TABLE_FILE);
353
+
354
+ if (bytes === null) {
355
+ throw new Error(`${DETECT_TABLE_FILE} is missing — run "serverk-bridge detect-table" and commit it`);
356
+ }
357
+
358
+ if (new TextDecoder().decode(bytes) !== renderDetectTable()) {
359
+ throw new Error(`${DETECT_TABLE_FILE} is out of date — run "serverk-bridge detect-table" and commit it`);
360
+ }
361
+
362
+ return [
363
+ ` detect: ${DETECT_TABLE_FILE} matches this bridge`,
364
+ ];
365
+ };
366
+
367
+ const validateRunnablePackage = async (
368
+ source: PackageSource,
369
+ manifest: RunnableManifest,
370
+ dir: string,
371
+ driverArg?: string,
372
+ ) => {
321
373
  const assets = readAssets(source, manifest);
322
374
  const guideFiles = manifest.guides.flatMap((guide) => [
323
375
  guide.bodyPath.ar,
@@ -331,6 +383,7 @@ const validateGamePackage = async (source: PackageSource, manifest: Manifest, di
331
383
  }
332
384
 
333
385
  const pack = readGuidePack(source, manifest);
386
+ const detectLines = botDetectLines(source, manifest);
334
387
  const driverPath = driverArg ? resolve(driverArg) : join(dir, "src/driver.ts");
335
388
 
336
389
  if (!existsSync(driverPath)) {
@@ -348,11 +401,22 @@ const validateGamePackage = async (source: PackageSource, manifest: Manifest, di
348
401
  validatePortActivation(manifest.container.ports, driver);
349
402
 
350
403
  const conditionalPorts = manifest.container.ports.filter((port) => port.activeWhen.length > 0).length;
404
+ const pairedPorts = manifest.container.ports.filter((port) => port.protocol === PortProtocol.TcpUdp).length;
405
+ const blockPorts = manifest.container.ports.filter((port) => port.count > 1).length;
351
406
 
352
407
  console.log(`${manifest.metadata.id} is valid`);
353
408
  console.log(
354
- ` manifest: ${manifest.container.ports.length} port(s), ${conditionalPorts} conditional, ${manifest.secrets.length} secret(s), ${manifest.guides.length} guide(s)`,
409
+ ` manifest: ${manifest.kind}, ${manifest.container.ports.length} port(s), ${conditionalPorts} conditional, ${pairedPorts} tcp+udp, ${blockPorts} block(s), ${(manifest.secrets ?? []).length} secret(s), ${manifest.guides.length} guide(s)`,
355
410
  );
411
+
412
+ for (const line of [
413
+ ...botPortLines(manifest),
414
+ ...botEnvironmentLines(manifest),
415
+ ...detectLines,
416
+ ]) {
417
+ console.log(line);
418
+ }
419
+
356
420
  console.log(
357
421
  ` assets: logo ${assets.logo.dimensions.width}x${assets.logo.dimensions.height}, banner ${assets.banner.dimensions.width}x${assets.banner.dimensions.height}`,
358
422
  );
@@ -399,7 +463,7 @@ export const runValidate = async (args: string[]) => {
399
463
 
400
464
  assertRegistryBridge(source);
401
465
 
402
- let document: Manifest | GuidePackage;
466
+ let document: Package;
403
467
 
404
468
  try {
405
469
  document = readPackageDocument(source);
@@ -407,11 +471,11 @@ export const runValidate = async (args: string[]) => {
407
471
  return invalidManifest(error);
408
472
  }
409
473
 
410
- if (document.kind === "GuidePackage") {
474
+ if (document.kind === PackageKind.Guide) {
411
475
  validateGuidePackage(source, document);
412
476
 
413
477
  return;
414
478
  }
415
479
 
416
- await validateGamePackage(source, document, dir, values.driver);
480
+ await validateRunnablePackage(source, document, dir, values.driver);
417
481
  };
@@ -6,6 +6,12 @@ export interface BridgeServer {
6
6
  memoryMb: number;
7
7
  cpuPercent: number;
8
8
  running: boolean;
9
+ readonly publicAddress: string | null;
10
+ }
11
+
12
+ export interface BridgePortBlock {
13
+ first: number;
14
+ count: number;
9
15
  }
10
16
 
11
17
  export interface BridgeFileEntry {
@@ -34,6 +40,7 @@ export interface BridgeExtractOptions {
34
40
  select?: string;
35
41
  selects?: string[];
36
42
  exclude?: string[];
43
+ dotfiles?: boolean;
37
44
  }
38
45
 
39
46
  export interface BridgeListOptions {
@@ -191,8 +198,11 @@ export interface BridgeLog {
191
198
  export interface BridgeContext {
192
199
  readonly server: BridgeServer;
193
200
  variable(key: string): string | null;
201
+ variables(): Readonly<Record<string, string>>;
194
202
  secret(key: string): string | null;
203
+ secrets(): Readonly<Record<string, string>>;
195
204
  port(key: string): number;
205
+ portBlock(key: string): BridgePortBlock;
196
206
  files: BridgeFiles;
197
207
  codec: BridgeCodecs;
198
208
  net: BridgeNet;
@@ -203,5 +213,6 @@ export interface BridgeContext {
203
213
  logs: BridgeLogs;
204
214
  setup: BridgeSetupReporter;
205
215
  emit(event: string, payload?: BridgeValues): void;
216
+ restart(): Promise<void>;
206
217
  log: BridgeLog;
207
218
  }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { PortProtocol, portCountOf, SOCKET_PROTOCOLS, socketProtocolsOf } from "./bridgePort";
3
+
4
+ describe("socketProtocolsOf", () => {
5
+ test("keeps a single socket protocol as it is", () => {
6
+ expect(socketProtocolsOf(PortProtocol.Tcp)).toEqual([
7
+ PortProtocol.Tcp,
8
+ ]);
9
+ expect(socketProtocolsOf(PortProtocol.Udp)).toEqual([
10
+ PortProtocol.Udp,
11
+ ]);
12
+ });
13
+
14
+ test("expands a pair into both socket protocols", () => {
15
+ expect(socketProtocolsOf(PortProtocol.TcpUdp)).toEqual([
16
+ PortProtocol.Tcp,
17
+ PortProtocol.Udp,
18
+ ]);
19
+ });
20
+
21
+ test("never hands out the shared list itself", () => {
22
+ const expanded = socketProtocolsOf(PortProtocol.TcpUdp);
23
+
24
+ expanded.pop();
25
+
26
+ expect(SOCKET_PROTOCOLS).toEqual([
27
+ PortProtocol.Tcp,
28
+ PortProtocol.Udp,
29
+ ]);
30
+ });
31
+ });
32
+
33
+ describe("portCountOf", () => {
34
+ test("reads a port stored before blocks existed as a single port", () => {
35
+ expect(portCountOf({})).toBe(1);
36
+ });
37
+
38
+ test("reads the count of a block", () => {
39
+ expect(
40
+ portCountOf({
41
+ count: 32,
42
+ }),
43
+ ).toBe(32);
44
+ });
45
+ });
@@ -1,8 +1,34 @@
1
1
  export enum PortProtocol {
2
2
  Tcp = "tcp",
3
3
  Udp = "udp",
4
+ TcpUdp = "tcp+udp",
4
5
  }
5
6
 
7
+ export const PORT_BLOCK_MAX_COUNT = 64;
8
+
9
+ export type SocketProtocol = PortProtocol.Tcp | PortProtocol.Udp;
10
+
11
+ export const SOCKET_PROTOCOLS = [
12
+ PortProtocol.Tcp,
13
+ PortProtocol.Udp,
14
+ ] as const satisfies readonly SocketProtocol[];
15
+
16
+ export const socketProtocolsOf = (protocol: PortProtocol): SocketProtocol[] => {
17
+ if (protocol === PortProtocol.TcpUdp) {
18
+ return [
19
+ ...SOCKET_PROTOCOLS,
20
+ ];
21
+ }
22
+
23
+ return [
24
+ protocol,
25
+ ];
26
+ };
27
+
28
+ export const portCountOf = (port: { count?: number | undefined }): number => {
29
+ return port.count ?? 1;
30
+ };
31
+
6
32
  export enum PortJoin {
7
33
  Combined = "combined",
8
34
  Separate = "separate",
@@ -1,7 +1,7 @@
1
1
  import { verifyAsset } from "../manifest/assets";
2
2
  import type { GuideEntry, GuideSurface, GuideTag } from "../manifest/guide";
3
3
  import type { LocalizedText } from "../manifest/localized";
4
- import type { GuidePackage, Manifest } from "../manifest/manifest";
4
+ import type { GuidePackage, RunnableManifest } from "../manifest/manifest";
5
5
  import type { PackageSource } from "../manifest/source";
6
6
  import type { GuideBlock, GuideLeafBlock } from "./blocks";
7
7
  import { type CompiledGuide, type CompiledGuideTag, compiledGuideSchema } from "./compiled";
@@ -140,7 +140,7 @@ export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entr
140
140
  };
141
141
  };
142
142
 
143
- export const readGuidePack = (source: PackageSource, manifest: Manifest): GuidePack | null => {
143
+ export const readGuidePack = (source: PackageSource, manifest: RunnableManifest): GuidePack | null => {
144
144
  if (manifest.guides.length === 0) {
145
145
  return null;
146
146
  }
@@ -4,6 +4,7 @@ export * from "./category";
4
4
  export * from "./guide";
5
5
  export * from "./image";
6
6
  export * from "./jsonSchema";
7
+ export * from "./kind";
7
8
  export * from "./localized";
8
9
  export * from "./manifest";
9
10
  export * from "./presence";
@@ -0,0 +1,5 @@
1
+ export enum PackageKind {
2
+ Game = "GamePackage",
3
+ Bot = "BotPackage",
4
+ Guide = "GuidePackage",
5
+ }