@serverkgg/bridge 0.0.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 (73) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +52 -0
  3. package/package.json +26 -0
  4. package/src/cli/compile.ts +75 -0
  5. package/src/cli/directorySource.ts +48 -0
  6. package/src/cli/main.ts +44 -0
  7. package/src/cli/validate.ts +94 -0
  8. package/src/codecs/index.ts +3 -0
  9. package/src/codecs/json.ts +42 -0
  10. package/src/codecs/properties.ts +75 -0
  11. package/src/codecs/ueIni.ts +235 -0
  12. package/src/context/bridgeContext.ts +120 -0
  13. package/src/context/index.ts +1 -0
  14. package/src/core/bridgeAction.ts +13 -0
  15. package/src/core/bridgeId.ts +3 -0
  16. package/src/core/bridgeKind.ts +11 -0
  17. package/src/core/bridgePort.ts +4 -0
  18. package/src/core/bridgeRuntime.ts +13 -0
  19. package/src/core/bridgeSecretError.ts +10 -0
  20. package/src/core/bridgeStream.ts +4 -0
  21. package/src/core/bridgeValue.ts +19 -0
  22. package/src/core/index.ts +8 -0
  23. package/src/main.ts +5 -0
  24. package/src/manifest/assets.ts +102 -0
  25. package/src/manifest/category.ts +10 -0
  26. package/src/manifest/checks.ts +50 -0
  27. package/src/manifest/dimensions.ts +106 -0
  28. package/src/manifest/guide.ts +36 -0
  29. package/src/manifest/image.ts +1 -0
  30. package/src/manifest/index.ts +9 -0
  31. package/src/manifest/localized.ts +8 -0
  32. package/src/manifest/manifest.ts +170 -0
  33. package/src/manifest/source.ts +89 -0
  34. package/src/modules/bridge.ts +53 -0
  35. package/src/modules/bridgeDriver.ts +19 -0
  36. package/src/modules/bridgeModule.ts +114 -0
  37. package/src/modules/index.ts +3 -0
  38. package/src/panel/bridgeConfirm.ts +5 -0
  39. package/src/panel/bridgeControl.ts +7 -0
  40. package/src/panel/bridgeFormTarget.ts +4 -0
  41. package/src/panel/bridgeIcon.ts +17 -0
  42. package/src/panel/bridgeLayout.ts +6 -0
  43. package/src/panel/bridgePanel.ts +107 -0
  44. package/src/panel/index.ts +6 -0
  45. package/src/probes/a2s.ts +170 -0
  46. package/src/probes/index.ts +1 -0
  47. package/src/protocol/bridgeConfig.ts +13 -0
  48. package/src/protocol/bridgeFrame.ts +91 -0
  49. package/src/protocol/bridgeLine.ts +40 -0
  50. package/src/protocol/bridgeManifest.ts +19 -0
  51. package/src/protocol/bridgeVersion.ts +11 -0
  52. package/src/protocol/index.ts +7 -0
  53. package/src/runtime/bridgeCodec.ts +78 -0
  54. package/src/runtime/bridgeConfigReader.ts +119 -0
  55. package/src/runtime/bridgeContext.ts +96 -0
  56. package/src/runtime/bridgeDriver.ts +179 -0
  57. package/src/runtime/bridgeEvents.ts +71 -0
  58. package/src/runtime/bridgeExec.ts +143 -0
  59. package/src/runtime/bridgeFiles.ts +412 -0
  60. package/src/runtime/bridgeLog.ts +15 -0
  61. package/src/runtime/bridgeNet.ts +59 -0
  62. package/src/runtime/bridgePath.ts +132 -0
  63. package/src/runtime/bridgePrivilege.ts +99 -0
  64. package/src/runtime/bridgeProbe.ts +14 -0
  65. package/src/runtime/bridgeRegistry.ts +311 -0
  66. package/src/runtime/bridgeServer.ts +233 -0
  67. package/src/runtime/bridgeState.ts +40 -0
  68. package/src/runtime/bridgeSupervisor.ts +498 -0
  69. package/src/runtime/bridgeZip.ts +122 -0
  70. package/src/runtime/index.ts +18 -0
  71. package/src/runtime/serve.ts +294 -0
  72. package/src/utils/glob.ts +62 -0
  73. package/src/utils/index.ts +1 -0
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ # Serverk Community License
2
+
3
+ Version 1.0
4
+
5
+ Copyright (c) 2026 Serverk — serverk.gg. All rights reserved.
6
+
7
+ ## Grant
8
+
9
+ Permission is granted, free of charge, to any person obtaining a copy of this software (the "Bridge") to:
10
+
11
+ 1. Use, copy, and modify the Bridge solely to develop, test, and operate game packages that run on the Serverk platform (`serverk.gg`).
12
+ 2. Contribute changes back to Serverk repositories.
13
+
14
+ ## Restrictions
15
+
16
+ 1. The Bridge may not be used, in whole or in part, with any platform or service other than Serverk, or to build or operate a product or service that competes with Serverk.
17
+ 2. The Bridge may not be redistributed, sublicensed, or sold, except as installed unmodified from the official npm package or as part of a contribution to Serverk repositories.
18
+ 3. This license grants no rights to Serverk names, logos, or trademarks.
19
+
20
+ ## Disclaimer
21
+
22
+ THE BRIDGE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SERVERK BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE BRIDGE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @serverkgg/bridge
2
+
3
+ جسر سيرفرك — المكتبة اللي تربط حزمة اللعبة بمنصة سيرفرك (Serverk). تكتب فيها الـ driver حق لعبتك، وتنضغط معه في ملف تنفيذي واحد يصير هو نقطة دخول كونتينر السيرفر.
4
+
5
+ The Serverk bridge — the library that connects a game package to the Serverk platform (`serverk.gg`). You write your game's driver against it, and it compiles with your code into a single binary that becomes the server container's entrypoint.
6
+
7
+ The bridge ships raw TypeScript and targets the Bun runtime only.
8
+
9
+ ## Entry points
10
+
11
+ - `@serverkgg/bridge` — the game-author API: `serve`, the `Bridge` namespace, capability modules, and panel types.
12
+ - `@serverkgg/bridge/protocol` — the wire contract between the bridge and the Serverk agent.
13
+ - `@serverkgg/bridge/manifest` — the `serverk.yml` schema and validation helpers.
14
+
15
+ ## Writing a game package
16
+
17
+ Export your driver from `src/driver.ts` and serve it from `src/main.ts`:
18
+
19
+ ```ts
20
+ import type { BridgeDriver } from "@serverkgg/bridge";
21
+
22
+ export const driver: BridgeDriver = {
23
+ install,
24
+ lifecycle,
25
+ };
26
+ ```
27
+
28
+ ```ts
29
+ import { serve } from "@serverkgg/bridge";
30
+ import { driver } from "./driver";
31
+
32
+ await serve(driver);
33
+ ```
34
+
35
+ The `src/driver.ts` convention is what lets `serverk-bridge validate` check your wiring without booting a server.
36
+
37
+ ## CLI
38
+
39
+ ```sh
40
+ bunx serverk-bridge validate
41
+ bunx serverk-bridge compile
42
+ ```
43
+
44
+ `validate` checks `serverk.yml` against the real platform schema, verifies the logo and banner, and validates the driver's panel wiring. `compile` produces `dist/bridge-amd64` and `dist/bridge-arm64`.
45
+
46
+ ## Compatibility
47
+
48
+ Only the major version carries compatibility meaning. The platform refuses a game image whose bridge major differs from the fleet's; minor and patch releases are always safe to pick up.
49
+
50
+ ## License
51
+
52
+ Source-available under the Serverk Community License: use it to build and run game packages for Serverk. See `LICENSE.md`.
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@serverkgg/bridge",
3
+ "version": "0.0.1",
4
+ "description": "Serverk game bridge — driver runtime, wire protocol, manifest schema, and maintainer CLI",
5
+ "homepage": "https://serverk.gg",
6
+ "license": "SEE LICENSE IN LICENSE.md",
7
+ "type": "module",
8
+ "bin": {
9
+ "serverk-bridge": "./src/cli/main.ts"
10
+ },
11
+ "files": [
12
+ "src"
13
+ ],
14
+ "exports": {
15
+ ".": "./src/main.ts",
16
+ "./protocol": "./src/protocol/index.ts",
17
+ "./manifest": "./src/manifest/index.ts",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "scripts": {
21
+ "tsc": "tsc"
22
+ },
23
+ "dependencies": {
24
+ "zod": "4.4.3"
25
+ }
26
+ }
@@ -0,0 +1,75 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ const TARGETS: Record<string, string> = {
4
+ amd64: "bun-linux-x64",
5
+ arm64: "bun-linux-arm64",
6
+ };
7
+
8
+ const HELP = `serverk-bridge compile [options]
9
+
10
+ options:
11
+ --arch <amd64|arm64> architecture to compile (repeatable, default: both)
12
+ --entry <path> entrypoint (default: src/main.ts)
13
+ --outdir <path> output directory (default: dist)
14
+ --help show this help`;
15
+
16
+ export const runCompile = async (args: string[]) => {
17
+ const { values } = parseArgs({
18
+ args,
19
+ options: {
20
+ arch: {
21
+ type: "string",
22
+ multiple: true,
23
+ },
24
+ entry: {
25
+ type: "string",
26
+ default: "src/main.ts",
27
+ },
28
+ outdir: {
29
+ type: "string",
30
+ default: "dist",
31
+ },
32
+ help: {
33
+ type: "boolean",
34
+ default: false,
35
+ },
36
+ },
37
+ });
38
+
39
+ if (values.help) {
40
+ console.log(HELP);
41
+
42
+ return;
43
+ }
44
+
45
+ const arches = values.arch?.length ? values.arch : Object.keys(TARGETS);
46
+
47
+ for (const arch of arches) {
48
+ const target = TARGETS[arch];
49
+
50
+ if (!target) {
51
+ throw new Error(`unknown arch "${arch}" — expected amd64 or arm64`);
52
+ }
53
+
54
+ const outfile = `${values.outdir}/bridge-${arch}`;
55
+ const child = Bun.spawn(
56
+ [
57
+ "bun",
58
+ "build",
59
+ "--compile",
60
+ `--target=${target}`,
61
+ values.entry,
62
+ "--outfile",
63
+ outfile,
64
+ ],
65
+ {
66
+ stderr: "inherit",
67
+ stdout: "inherit",
68
+ },
69
+ );
70
+
71
+ if ((await child.exited) !== 0) {
72
+ throw new Error(`compile failed for ${arch}`);
73
+ }
74
+ }
75
+ };
@@ -0,0 +1,48 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { PackageSource } from "../manifest";
4
+
5
+ const EXCLUDED = new Set([
6
+ ".git",
7
+ "dist",
8
+ "node_modules",
9
+ "tmp",
10
+ "vendor",
11
+ ]);
12
+
13
+ const walk = (root: string, prefix: string, paths: string[]) => {
14
+ for (const entry of readdirSync(join(root, prefix), {
15
+ withFileTypes: true,
16
+ })) {
17
+ if (EXCLUDED.has(entry.name)) {
18
+ continue;
19
+ }
20
+
21
+ const path = prefix ? `${prefix}/${entry.name}` : entry.name;
22
+
23
+ if (entry.isDirectory()) {
24
+ walk(root, path, paths);
25
+ } else if (entry.isFile()) {
26
+ paths.push(path);
27
+ }
28
+ }
29
+ };
30
+
31
+ export const directorySource = (root: string): PackageSource => {
32
+ return {
33
+ list() {
34
+ const paths: string[] = [];
35
+
36
+ walk(root, "", paths);
37
+
38
+ return paths;
39
+ },
40
+ read(path) {
41
+ try {
42
+ return new Uint8Array(readFileSync(join(root, path)));
43
+ } catch {
44
+ return null;
45
+ }
46
+ },
47
+ };
48
+ };
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bun
2
+ import { runCompile } from "./compile";
3
+ import { runValidate } from "./validate";
4
+
5
+ const USAGE = `serverk-bridge <command>
6
+
7
+ commands:
8
+ validate validate serverk.yml, assets, and driver wiring
9
+ compile compile the bridge binary for amd64 and arm64
10
+
11
+ run "serverk-bridge <command> --help" for options`;
12
+
13
+ const [command, ...args] = process.argv.slice(2);
14
+
15
+ try {
16
+ switch (command) {
17
+ case "validate": {
18
+ await runValidate(args);
19
+
20
+ break;
21
+ }
22
+
23
+ case "compile": {
24
+ await runCompile(args);
25
+
26
+ break;
27
+ }
28
+
29
+ case undefined:
30
+ case "--help": {
31
+ console.log(USAGE);
32
+
33
+ break;
34
+ }
35
+
36
+ default: {
37
+ console.error(`unknown command "${command}"\n\n${USAGE}`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+ } catch (error) {
42
+ console.error(error instanceof Error ? error.message : String(error));
43
+ process.exit(1);
44
+ }
@@ -0,0 +1,94 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { parseArgs } from "node:util";
5
+ import { z } from "zod";
6
+ import { declaredFiles, type Manifest, readAssets, readPackage } from "../manifest";
7
+ import type { BridgeDriver } from "../modules";
8
+ import { validateDriver } from "../runtime/bridgeDriver";
9
+ import { directorySource } from "./directorySource";
10
+
11
+ const HELP = `serverk-bridge validate [options]
12
+
13
+ options:
14
+ --dir <path> game package directory (default: current directory)
15
+ --driver <path> driver module (default: <dir>/src/driver.ts)
16
+ --help show this help`;
17
+
18
+ export const runValidate = async (args: string[]) => {
19
+ const { values } = parseArgs({
20
+ args,
21
+ options: {
22
+ dir: {
23
+ type: "string",
24
+ default: ".",
25
+ },
26
+ driver: {
27
+ type: "string",
28
+ },
29
+ help: {
30
+ type: "boolean",
31
+ default: false,
32
+ },
33
+ },
34
+ });
35
+
36
+ if (values.help) {
37
+ console.log(HELP);
38
+
39
+ return;
40
+ }
41
+
42
+ const dir = resolve(values.dir);
43
+ const source = directorySource(dir);
44
+
45
+ let manifest: Manifest;
46
+
47
+ try {
48
+ manifest = readPackage(source).manifest;
49
+ } catch (error) {
50
+ if (error instanceof z.ZodError) {
51
+ throw new Error(`serverk.yml is invalid\n${z.prettifyError(error)}`);
52
+ }
53
+
54
+ throw error;
55
+ }
56
+
57
+ const assets = readAssets(source, manifest);
58
+ const guideFiles = manifest.guides.flatMap((guide) => [
59
+ guide.body.ar,
60
+ guide.body.en,
61
+ ]);
62
+
63
+ for (const path of declaredFiles(manifest, guideFiles)) {
64
+ if (source.read(path) === null) {
65
+ throw new Error(`declared file "${path}" is missing`);
66
+ }
67
+ }
68
+
69
+ const driverPath = values.driver ? resolve(values.driver) : join(dir, "src/driver.ts");
70
+
71
+ if (!existsSync(driverPath)) {
72
+ throw new Error(`no driver module at ${driverPath} — export your BridgeDriver from src/driver.ts`);
73
+ }
74
+
75
+ const imported = await import(pathToFileURL(driverPath).href);
76
+ const driver: BridgeDriver | undefined = imported.driver ?? imported.default;
77
+
78
+ if (!driver || typeof driver !== "object") {
79
+ throw new Error(`${driverPath} must export a BridgeDriver named "driver"`);
80
+ }
81
+
82
+ validateDriver(driver);
83
+
84
+ console.log(`${manifest.metadata.id} is valid`);
85
+ console.log(
86
+ ` manifest: ${manifest.runtime.ports.length} port(s), ${manifest.secrets.length} secret(s), ${manifest.guides.length} guide(s)`,
87
+ );
88
+ console.log(
89
+ ` assets: logo ${assets.logo.dimensions.width}x${assets.logo.dimensions.height}, banner ${assets.banner.dimensions.width}x${assets.banner.dimensions.height}`,
90
+ );
91
+ console.log(
92
+ ` driver: ${Object.keys(driver.modules ?? {}).length} panel module(s), ${driver.panel?.tabs.length ?? 0} tab(s)`,
93
+ );
94
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./json";
2
+ export * from "./properties";
3
+ export * from "./ueIni";
@@ -0,0 +1,42 @@
1
+ import type { BridgeValue } from "../core";
2
+
3
+ const isScalar = (value: unknown): value is BridgeValue => {
4
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
5
+ };
6
+
7
+ const toObject = (contents: string) => {
8
+ if (contents.trim().length === 0) {
9
+ return {};
10
+ }
11
+
12
+ const parsed: unknown = JSON.parse(contents);
13
+
14
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15
+ return {};
16
+ }
17
+
18
+ return parsed as Record<string, unknown>;
19
+ };
20
+
21
+ export const parseJson = (contents: string) => {
22
+ const values: Record<string, BridgeValue> = {};
23
+
24
+ for (const [key, value] of Object.entries(toObject(contents))) {
25
+ if (isScalar(value)) {
26
+ values[key] = value;
27
+ }
28
+ }
29
+
30
+ return values;
31
+ };
32
+
33
+ export const mergeJson = (contents: string, values: Record<string, BridgeValue>) => {
34
+ return `${JSON.stringify(
35
+ {
36
+ ...toObject(contents),
37
+ ...values,
38
+ },
39
+ null,
40
+ 2,
41
+ )}\n`;
42
+ };
@@ -0,0 +1,75 @@
1
+ const COMMENT_MARKERS = [
2
+ "#",
3
+ "!",
4
+ ];
5
+
6
+ const SEPARATOR = "=";
7
+
8
+ const isComment = (line: string) => {
9
+ const trimmed = line.trimStart();
10
+
11
+ return trimmed.length === 0 || COMMENT_MARKERS.some((marker) => trimmed.startsWith(marker));
12
+ };
13
+
14
+ export const parseProperties = (contents: string) => {
15
+ const values: Record<string, string> = {};
16
+
17
+ for (const line of contents.split("\n")) {
18
+ if (isComment(line)) {
19
+ continue;
20
+ }
21
+
22
+ const separator = line.indexOf(SEPARATOR);
23
+
24
+ if (separator === -1) {
25
+ continue;
26
+ }
27
+
28
+ values[line.slice(0, separator).trim()] = line.slice(separator + 1).trim();
29
+ }
30
+
31
+ return values;
32
+ };
33
+
34
+ export const mergeProperties = (contents: string, values: Record<string, string>) => {
35
+ const pending = new Map(Object.entries(values));
36
+ const lines = contents.length === 0 ? [] : contents.split("\n");
37
+ const output: string[] = [];
38
+
39
+ for (const line of lines) {
40
+ if (isComment(line)) {
41
+ output.push(line);
42
+
43
+ continue;
44
+ }
45
+
46
+ const separator = line.indexOf(SEPARATOR);
47
+
48
+ if (separator === -1) {
49
+ output.push(line);
50
+
51
+ continue;
52
+ }
53
+
54
+ const key = line.slice(0, separator).trim();
55
+
56
+ if (!pending.has(key)) {
57
+ output.push(line);
58
+
59
+ continue;
60
+ }
61
+
62
+ output.push(`${key}${SEPARATOR}${pending.get(key)}`);
63
+ pending.delete(key);
64
+ }
65
+
66
+ while (output.length > 0 && output.at(-1)?.length === 0) {
67
+ output.pop();
68
+ }
69
+
70
+ for (const [key, value] of pending) {
71
+ output.push(`${key}${SEPARATOR}${value}`);
72
+ }
73
+
74
+ return `${output.join("\n")}\n`;
75
+ };