@serverkgg/bridge 0.0.10 → 0.0.12
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 +3 -1
- package/src/blog/articleFrontmatter.test.ts +95 -0
- package/src/blog/articleFrontmatter.ts +99 -0
- package/src/blog/compile.test.ts +148 -0
- package/src/blog/compile.ts +172 -0
- package/src/blog/compiled.ts +22 -0
- package/src/blog/index.ts +3 -0
- package/src/cli/validate.test.ts +116 -1
- package/src/cli/validate.ts +57 -4
- package/src/context/bridgeContext.ts +20 -0
- package/src/guides/compile.ts +70 -54
- package/src/manifest/guide.ts +1 -0
- package/src/manifest/manifest.test.ts +33 -0
- package/src/panel/bridgeControl.ts +1 -0
- package/src/panel/bridgeDetail.ts +1 -0
- package/src/panel/bridgePanel.ts +2 -0
- package/src/probes/index.ts +9 -0
- package/src/probes/minecraftPing.test.ts +344 -0
- package/src/probes/minecraftPing.ts +283 -0
- package/src/probes/raknetPing.test.ts +197 -0
- package/src/probes/raknetPing.ts +228 -0
- package/src/protocol/bridgeLimits.ts +8 -0
- package/src/runtime/bridgeDriver.test.ts +240 -0
- package/src/runtime/bridgeDriver.ts +137 -0
- package/src/runtime/bridgeProbe.ts +13 -1
- package/src/runtime/bridgeSetup.test.ts +387 -1
- package/src/runtime/bridgeSetup.ts +247 -7
- package/src/runtime/bridgeSetupHandlers.test.ts +303 -0
- package/src/runtime/bridgeSetupHandlers.ts +71 -0
- package/src/runtime/serve.ts +8 -41
- package/src/wire/panel.test.ts +62 -0
- package/src/wire/panel.ts +3 -0
package/src/cli/validate.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { type
|
|
6
|
+
import { type ArticlePack, compileArticles } from "../blog";
|
|
7
|
+
import { GuideOpenTab, type GuideOpenTarget, type GuidePack, readGuidePack, readGuidePackage } from "../guides";
|
|
7
8
|
import {
|
|
8
9
|
declaredFiles,
|
|
9
10
|
type GuidePackage,
|
|
@@ -13,8 +14,8 @@ import {
|
|
|
13
14
|
readAssets,
|
|
14
15
|
readPackageDocument,
|
|
15
16
|
} from "../manifest";
|
|
16
|
-
import type { BridgeDriver } from "../modules";
|
|
17
|
-
import { isBridgeEventName } from "../protocol";
|
|
17
|
+
import type { BridgeDriver, BridgeSetupModule } from "../modules";
|
|
18
|
+
import { type BridgeSetupStep, BridgeSetupStepKind, isBridgeEventName } from "../protocol";
|
|
18
19
|
import { validateDriver, validatePortActivation } from "../runtime/bridgeDriver";
|
|
19
20
|
import { directorySource } from "./directorySource";
|
|
20
21
|
|
|
@@ -131,6 +132,10 @@ const reportGuides = (pack: GuidePack | null) => {
|
|
|
131
132
|
}
|
|
132
133
|
};
|
|
133
134
|
|
|
135
|
+
const reportArticles = (pack: ArticlePack) => {
|
|
136
|
+
console.log(` articles: ${pack.drafts.length} article(s), ${pack.images.length} image(s)`);
|
|
137
|
+
};
|
|
138
|
+
|
|
134
139
|
const validateGuidePackage = (source: PackageSource, document: GuidePackage) => {
|
|
135
140
|
const guideFiles = document.guides.flatMap((guide) => [
|
|
136
141
|
guide.bodyPath.ar,
|
|
@@ -144,9 +149,11 @@ const validateGuidePackage = (source: PackageSource, document: GuidePackage) =>
|
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
const pack = readGuidePackage(source, document);
|
|
152
|
+
const articles = compileArticles(source);
|
|
147
153
|
|
|
148
154
|
console.log(`${document.metadata.id} is valid`);
|
|
149
155
|
reportGuides(pack);
|
|
156
|
+
reportArticles(articles);
|
|
150
157
|
};
|
|
151
158
|
|
|
152
159
|
const ACTION_TARGET_BUDGET = 80;
|
|
@@ -186,6 +193,49 @@ const reportActionTargets = (driver: BridgeDriver) => {
|
|
|
186
193
|
}
|
|
187
194
|
};
|
|
188
195
|
|
|
196
|
+
const openTargetOf = (target: GuideOpenTarget) => {
|
|
197
|
+
if (target.tab === GuideOpenTab.Panel) {
|
|
198
|
+
return `panel tab "${target.tabId}"`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (target.tab === GuideOpenTab.Files) {
|
|
202
|
+
return target.path ? `files at "${target.path}"` : "files";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return target.tab;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const setupTargetOf = (step: BridgeSetupStep) => {
|
|
209
|
+
if (step.kind === BridgeSetupStepKind.Form) {
|
|
210
|
+
const fields = step.fields ?? [];
|
|
211
|
+
|
|
212
|
+
return `→ ${step.tab}/${step.section}${fields.length === 0 ? "" : ` [${fields.join(", ")}]`}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (step.kind === BridgeSetupStepKind.Open) {
|
|
216
|
+
return `→ ${openTargetOf(step.target)}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return step.requiresRunning ? "→ driver (needs the game running)" : "→ driver";
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const setupReportLines = (setup: BridgeSetupModule | undefined): string[] => {
|
|
223
|
+
if (!setup) {
|
|
224
|
+
return [
|
|
225
|
+
" setup: none",
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return [
|
|
230
|
+
` setup: ${setup.steps.length} step(s)`,
|
|
231
|
+
...setup.steps.map((step) => {
|
|
232
|
+
const target = setupTargetOf(step);
|
|
233
|
+
|
|
234
|
+
return ` ${step.id} — ${step.kind}, ${step.required === false ? "optional" : "required"}${target === "" ? "" : ` ${target}`}`;
|
|
235
|
+
}),
|
|
236
|
+
];
|
|
237
|
+
};
|
|
238
|
+
|
|
189
239
|
const validateGamePackage = async (source: PackageSource, manifest: Manifest, dir: string, driverArg?: string) => {
|
|
190
240
|
const assets = readAssets(source, manifest);
|
|
191
241
|
const guideFiles = manifest.guides.flatMap((guide) => [
|
|
@@ -230,7 +280,10 @@ const validateGamePackage = async (source: PackageSource, manifest: Manifest, di
|
|
|
230
280
|
` driver: ${Object.keys(driver.modules ?? {}).length} panel module(s), ${driver.panel?.tabs.length ?? 0} tab(s)`,
|
|
231
281
|
);
|
|
232
282
|
console.log(` pending: ${driver.pending ? `declared (${driver.pending.extensions.length} extension(s))` : "none"}`);
|
|
233
|
-
|
|
283
|
+
for (const line of setupReportLines(driver.setup)) {
|
|
284
|
+
console.log(line);
|
|
285
|
+
}
|
|
286
|
+
|
|
234
287
|
reportEvents(driver);
|
|
235
288
|
reportActionTargets(driver);
|
|
236
289
|
};
|
|
@@ -91,8 +91,28 @@ export interface BridgeA2sInfo {
|
|
|
91
91
|
latencyMs: number;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
export interface BridgeMinecraftPingStatus {
|
|
95
|
+
players: BridgePingPlayers;
|
|
96
|
+
version: string | null;
|
|
97
|
+
description: string | null;
|
|
98
|
+
latencyMs: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface BridgeRaknetPingStatus {
|
|
102
|
+
players: BridgePingPlayers;
|
|
103
|
+
edition: string | null;
|
|
104
|
+
motd: string | null;
|
|
105
|
+
subMotd: string | null;
|
|
106
|
+
version: string | null;
|
|
107
|
+
protocol: number | null;
|
|
108
|
+
gamemode: string | null;
|
|
109
|
+
latencyMs: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
94
112
|
export interface BridgeProbes {
|
|
95
113
|
a2s(port: number): Promise<BridgeA2sInfo>;
|
|
114
|
+
minecraftPing(port: number): Promise<BridgeMinecraftPingStatus>;
|
|
115
|
+
raknetPing(port: number): Promise<BridgeRaknetPingStatus>;
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
export interface BridgeCommandOptions {
|
package/src/guides/compile.ts
CHANGED
|
@@ -52,49 +52,65 @@ const compileTags = (guideTags: GuideTag[], tags: string[]): CompiledGuideTag[]
|
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
export
|
|
56
|
-
|
|
55
|
+
export interface MarkdownBodyReader {
|
|
56
|
+
files: string[];
|
|
57
|
+
commands: string[];
|
|
58
|
+
images: Map<string, GuideImageAsset>;
|
|
59
|
+
read(path: string, text?: string): ParsedBlock[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const markdownBodyReader = (source: PackageSource): MarkdownBodyReader => {
|
|
57
63
|
const files: string[] = [];
|
|
58
64
|
const commands: string[] = [];
|
|
59
|
-
const
|
|
65
|
+
const images = new Map<string, GuideImageAsset>();
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
return {
|
|
68
|
+
files,
|
|
69
|
+
commands,
|
|
70
|
+
images,
|
|
71
|
+
read(path, text) {
|
|
72
|
+
const body = source.read(path);
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
if (!body) {
|
|
75
|
+
throw new Error(`missing markdown body ${path}`);
|
|
76
|
+
}
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
files.push(path);
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
const parsed = parseGuideMarkdown(path, text ?? new TextDecoder().decode(body));
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
commands.push(...parsed.commands);
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
for (const imagePath of parsed.images) {
|
|
85
|
+
if (images.has(imagePath)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
const imageBytes = source.read(imagePath);
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
if (!imageBytes) {
|
|
92
|
+
throw new Error(`missing guide image ${imagePath}`);
|
|
93
|
+
}
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
const verified = verifyAsset(imagePath, imageBytes);
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
images.set(imagePath, {
|
|
98
|
+
path: imagePath,
|
|
99
|
+
bytes: imageBytes,
|
|
100
|
+
mediaType: verified.mediaType,
|
|
101
|
+
width: verified.dimensions?.width ?? null,
|
|
102
|
+
height: verified.dimensions?.height ?? null,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
95
105
|
|
|
96
|
-
|
|
106
|
+
return parsed.blocks;
|
|
107
|
+
},
|
|
97
108
|
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entries: GuideEntry[]): GuidePack => {
|
|
112
|
+
const drafts: DraftGuide[] = [];
|
|
113
|
+
const reader = markdownBodyReader(source);
|
|
98
114
|
|
|
99
115
|
for (const entry of entries) {
|
|
100
116
|
drafts.push({
|
|
@@ -105,8 +121,8 @@ export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entr
|
|
|
105
121
|
surfaces: entry.surfaces,
|
|
106
122
|
videoId: entry.video ?? null,
|
|
107
123
|
body: {
|
|
108
|
-
ar:
|
|
109
|
-
en:
|
|
124
|
+
ar: reader.read(entry.bodyPath.ar),
|
|
125
|
+
en: reader.read(entry.bodyPath.en),
|
|
110
126
|
},
|
|
111
127
|
});
|
|
112
128
|
}
|
|
@@ -114,13 +130,13 @@ export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entr
|
|
|
114
130
|
return {
|
|
115
131
|
drafts,
|
|
116
132
|
files: [
|
|
117
|
-
...files,
|
|
118
|
-
...
|
|
133
|
+
...reader.files,
|
|
134
|
+
...reader.images.keys(),
|
|
119
135
|
],
|
|
120
136
|
images: [
|
|
121
|
-
...
|
|
137
|
+
...reader.images.values(),
|
|
122
138
|
],
|
|
123
|
-
commands,
|
|
139
|
+
commands: reader.commands,
|
|
124
140
|
};
|
|
125
141
|
};
|
|
126
142
|
|
|
@@ -142,10 +158,7 @@ export interface ResolvedGuideImage {
|
|
|
142
158
|
height: number | null;
|
|
143
159
|
}
|
|
144
160
|
|
|
145
|
-
export const
|
|
146
|
-
drafts: DraftGuide[],
|
|
147
|
-
resolved: Map<string, ResolvedGuideImage>,
|
|
148
|
-
): CompiledGuide[] => {
|
|
161
|
+
export const resolveGuideBlocks = (blocks: ParsedBlock[], resolved: Map<string, ResolvedGuideImage>): GuideBlock[] => {
|
|
149
162
|
const resolveLeaf = (block: ParsedLeafBlock): GuideLeafBlock => {
|
|
150
163
|
if (block.type !== "image") {
|
|
151
164
|
return block;
|
|
@@ -166,27 +179,30 @@ export const resolveGuideImages = (
|
|
|
166
179
|
};
|
|
167
180
|
};
|
|
168
181
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
182
|
+
return blocks.map((block) => {
|
|
183
|
+
if (block.type !== "conditional") {
|
|
184
|
+
return resolveLeaf(block);
|
|
185
|
+
}
|
|
174
186
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
return {
|
|
188
|
+
type: "conditional",
|
|
189
|
+
key: block.key,
|
|
190
|
+
when: block.when.map(resolveLeaf),
|
|
191
|
+
otherwise: block.otherwise.map(resolveLeaf),
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
};
|
|
183
195
|
|
|
196
|
+
export const resolveGuideImages = (
|
|
197
|
+
drafts: DraftGuide[],
|
|
198
|
+
resolved: Map<string, ResolvedGuideImage>,
|
|
199
|
+
): CompiledGuide[] => {
|
|
184
200
|
return drafts.map((draft) => {
|
|
185
201
|
return compiledGuideSchema.parse({
|
|
186
202
|
...draft,
|
|
187
203
|
body: {
|
|
188
|
-
ar:
|
|
189
|
-
en:
|
|
204
|
+
ar: resolveGuideBlocks(draft.body.ar, resolved),
|
|
205
|
+
en: resolveGuideBlocks(draft.body.en, resolved),
|
|
190
206
|
},
|
|
191
207
|
});
|
|
192
208
|
});
|
package/src/manifest/guide.ts
CHANGED
|
@@ -10,6 +10,26 @@ const text = (value: string) => ({
|
|
|
10
10
|
en: value,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
const guide = (id: string, extra: Record<string, unknown> = {}) => ({
|
|
14
|
+
id,
|
|
15
|
+
title: text(id),
|
|
16
|
+
summary: text(id),
|
|
17
|
+
bodyPath: {
|
|
18
|
+
ar: `guides/${id}/ar.md`,
|
|
19
|
+
en: `guides/${id}/en.md`,
|
|
20
|
+
},
|
|
21
|
+
...extra,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const guidePackage = (guides: unknown[]) => ({
|
|
25
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
26
|
+
kind: "GuidePackage",
|
|
27
|
+
metadata: {
|
|
28
|
+
id: "platform",
|
|
29
|
+
},
|
|
30
|
+
guides,
|
|
31
|
+
});
|
|
32
|
+
|
|
13
33
|
const base = () => ({
|
|
14
34
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
15
35
|
kind: "GamePackage",
|
|
@@ -586,6 +606,19 @@ describe("packageSchema", () => {
|
|
|
586
606
|
expect(result.success).toBe(false);
|
|
587
607
|
expect(result.error && z.prettifyError(result.error)).toContain('tag "start" is not declared in guideTags');
|
|
588
608
|
});
|
|
609
|
+
|
|
610
|
+
test("carries up to 64 guides", () => {
|
|
611
|
+
const many = (count: number) =>
|
|
612
|
+
Array.from(
|
|
613
|
+
{
|
|
614
|
+
length: count,
|
|
615
|
+
},
|
|
616
|
+
(_, index) => guide(`guide-${index}`),
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
expect(guidePackageSchema.safeParse(guidePackage(many(64))).success).toBe(true);
|
|
620
|
+
expect(guidePackageSchema.safeParse(guidePackage(many(65))).success).toBe(false);
|
|
621
|
+
});
|
|
589
622
|
});
|
|
590
623
|
|
|
591
624
|
describe("presence", () => {
|
package/src/panel/bridgePanel.ts
CHANGED
package/src/probes/index.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
BridgeA2sInfo,
|
|
3
|
+
BridgeMinecraftPingStatus,
|
|
4
|
+
BridgePingPlayers,
|
|
5
|
+
BridgeRaknetPingStatus,
|
|
6
|
+
} from "../context";
|
|
7
|
+
export { BridgeProbeCode, BridgeProbeError } from "../core";
|
|
1
8
|
export * from "./a2s";
|
|
9
|
+
export * from "./minecraftPing";
|
|
10
|
+
export * from "./raknetPing";
|