@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.
- package/package.json +10 -3
- package/src/cli/main.ts +11 -1
- package/src/cli/schema.ts +62 -0
- package/src/cli/validate.ts +119 -36
- package/src/context/bridgeContext.ts +11 -1
- package/src/core/bridgeAction.ts +13 -0
- package/src/core/bridgeFailure.ts +16 -0
- package/src/core/bridgeKind.ts +2 -0
- package/src/core/bridgeNetError.ts +10 -0
- package/src/core/index.ts +2 -0
- package/src/guides/blocks.ts +167 -0
- package/src/guides/compile.ts +193 -0
- package/src/guides/compiled.ts +28 -0
- package/src/guides/index.ts +6 -0
- package/src/guides/markdown.test.ts +374 -0
- package/src/guides/markdown.ts +769 -0
- package/src/guides/variables.ts +52 -0
- package/src/guides/version.ts +1 -0
- package/src/main.ts +2 -1
- package/src/manifest/assets.ts +4 -4
- package/src/manifest/guide.ts +23 -18
- package/src/manifest/index.ts +3 -2
- package/src/manifest/jsonSchema.ts +16 -0
- package/src/manifest/localized.ts +7 -0
- package/src/manifest/manifest.test.ts +366 -0
- package/src/manifest/manifest.ts +354 -120
- package/src/manifest/primitives.ts +63 -0
- package/src/manifest/source.ts +5 -4
- package/src/modules/bridge.ts +19 -3
- package/src/modules/bridgeDriver.ts +4 -0
- package/src/modules/bridgeModule.ts +22 -24
- package/src/panel/bridgeAccess.ts +3 -0
- package/src/panel/bridgePanel.ts +6 -0
- package/src/panel/index.ts +1 -0
- package/src/protocol/bridgeCatalog.ts +56 -0
- package/src/protocol/bridgeEgress.ts +53 -0
- package/src/protocol/bridgeFrame.ts +17 -1
- package/src/protocol/bridgeLine.ts +41 -6
- package/src/protocol/bridgeManifest.ts +15 -1
- package/src/protocol/bridgeVersion.ts +1 -1
- package/src/protocol/index.ts +3 -0
- package/src/runtime/bridgeContext.ts +29 -9
- package/src/runtime/bridgeDriver.ts +1 -0
- package/src/runtime/bridgeEgress.ts +360 -0
- package/src/runtime/bridgeExec.ts +15 -5
- package/src/runtime/bridgeFiles.ts +58 -21
- package/src/runtime/bridgeLog.ts +26 -3
- package/src/runtime/bridgeNet.ts +75 -22
- package/src/runtime/bridgeRegistry.ts +51 -6
- package/src/runtime/bridgeServer.ts +48 -1
- package/src/runtime/bridgeSupervisor.ts +6 -6
- package/src/runtime/index.ts +0 -17
- package/src/runtime/serve.ts +44 -3
- package/src/terminal/bridgeTerminalArg.ts +11 -0
- package/src/terminal/bridgeTerminalCommand.ts +11 -0
- package/src/terminal/bridgeTerminalLevel.ts +6 -0
- package/src/terminal/bridgeTerminalRule.ts +8 -0
- package/src/terminal/index.ts +4 -0
- 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 {
|
|
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
|
|
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,
|
|
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 {
|
|
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:
|
|
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
|
});
|
package/src/runtime/index.ts
CHANGED
|
@@ -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";
|
package/src/runtime/serve.ts
CHANGED
|
@@ -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
|
+
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
|
+
}
|
package/src/manifest/checks.ts
DELETED
|
@@ -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
|
-
};
|