@serverkgg/bridge 0.1.11 → 0.1.13
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 +1 -1
- package/rules/package.md +2 -1
- package/src/bot/botDeploy.test.ts +57 -0
- package/src/bot/botEnvironment.test.ts +21 -0
- package/src/bot/botEnvironment.ts +10 -0
- package/src/bot/botRun.test.ts +296 -0
- package/src/bot/botRun.ts +109 -0
- package/src/bot/botState.test.ts +34 -1
- package/src/bot/botState.ts +1 -0
- package/src/bot/botVariables.test.ts +22 -0
- package/src/bot/botVariables.ts +3 -0
- package/src/bot/detect/detect.test.ts +172 -0
- package/src/bot/detect/detect.ts +31 -0
- package/src/bot/detect/detectResult.ts +1 -0
- package/src/bot/detect/detectTable.test.ts +25 -2
- package/src/bot/detect/detectTable.ts +24 -1
- package/src/bot/detect/keys.test.ts +7 -0
- package/src/bot/detect/keys.ts +3 -0
- package/src/bot/detect/serverkJson.test.ts +21 -0
- package/src/bot/detect/serverkJson.ts +13 -1
- package/src/bot/index.ts +1 -0
- package/src/cli/link.ts +3 -3
- package/src/cli/main.ts +8 -0
- package/src/cli/readme.test.ts +470 -0
- package/src/cli/readme.ts +570 -0
- package/src/cli/validate.test.ts +41 -1
- package/src/cli/validate.ts +25 -0
- package/src/context/bridgeContext.ts +1 -0
- package/src/modules/bridgeModule.ts +1 -0
- package/src/protocol/bridgeManifest.ts +1 -0
- package/src/runtime/bridgeContext.test.ts +6 -0
- package/src/runtime/bridgeContext.ts +7 -2
- package/src/runtime/bridgeExec.test.ts +150 -0
- package/src/runtime/bridgeExec.ts +67 -19
- package/src/runtime/bridgeLineSpacing.test.ts +132 -0
- package/src/runtime/bridgeLineSpacing.ts +49 -0
- package/src/runtime/bridgeLog.ts +9 -0
- package/src/runtime/bridgeRedaction.test.ts +28 -2
- package/src/runtime/bridgeRedaction.ts +7 -1
- package/src/runtime/bridgeRegistry.test.ts +30 -0
- package/src/runtime/bridgeRegistry.ts +1 -0
- package/src/runtime/bridgeScopedContext.ts +3 -0
- package/src/runtime/bridgeSetupHandlers.test.ts +3 -0
- package/src/runtime/bridgeSupervisor.ts +7 -9
- package/src/runtime/serve.ts +9 -2
- package/src/steam/steamcmdNarrator.test.ts +18 -5
- package/src/steam/steamcmdNarrator.ts +26 -2
- package/src/wine/protonCommand.test.ts +17 -1
- package/src/wine/protonCommand.ts +7 -1
- package/src/wire/panel.test.ts +32 -0
- package/src/wire/panel.ts +1 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { GameCategory, MANIFEST_SCHEMA_VERSION, PackageKind, readPackageDocument } from "../manifest";
|
|
7
|
+
import { directorySource } from "./directorySource";
|
|
8
|
+
import { README_FILE, renderReadme, runReadme } from "./readme";
|
|
9
|
+
|
|
10
|
+
const SIBLINGS_ROOT = fileURLToPath(new URL("../../../../../", import.meta.url));
|
|
11
|
+
|
|
12
|
+
const SIBLINGS = [
|
|
13
|
+
"minecraft-java",
|
|
14
|
+
"bot-node",
|
|
15
|
+
"platform",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const HEADINGS = [
|
|
19
|
+
"## Layout",
|
|
20
|
+
"## Guides",
|
|
21
|
+
"## Develop",
|
|
22
|
+
"## Contribute",
|
|
23
|
+
"## License",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const FULL_SCRIPTS = {
|
|
27
|
+
prepare: "git config core.hooksPath .githooks",
|
|
28
|
+
postinstall: "serverk-bridge link && (serverk-bridge rules || true)",
|
|
29
|
+
compile: "serverk-bridge compile",
|
|
30
|
+
validate: "serverk-bridge validate",
|
|
31
|
+
schema: "serverk-bridge schema",
|
|
32
|
+
check: "bunx biome check",
|
|
33
|
+
fix: "bunx biome check --write --unsafe",
|
|
34
|
+
tsc: "tsc",
|
|
35
|
+
test: "bun test --parallel",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const HOOK = `#!/usr/bin/env bash
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
echo "pre-commit: fix"
|
|
42
|
+
bun run fix
|
|
43
|
+
|
|
44
|
+
echo "pre-commit: validate"
|
|
45
|
+
bun run validate
|
|
46
|
+
|
|
47
|
+
echo "pre-commit: schema"
|
|
48
|
+
bunx serverk-bridge schema --check
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const FOOTER_EN = "Generated by `serverk-bridge readme` from `serverk.yml`; edit the manifest, not this file.";
|
|
52
|
+
|
|
53
|
+
const LICENSE_EN = "The code is public to read. All rights are reserved by Serverk, and no license is granted.";
|
|
54
|
+
|
|
55
|
+
const guideOf = (id: string) => ({
|
|
56
|
+
id,
|
|
57
|
+
title: {
|
|
58
|
+
ar: `شرح ${id}`,
|
|
59
|
+
en: `Guide ${id}`,
|
|
60
|
+
},
|
|
61
|
+
summary: {
|
|
62
|
+
ar: "ملخص الشرح.",
|
|
63
|
+
en: "The guide summary.",
|
|
64
|
+
},
|
|
65
|
+
bodyPath: {
|
|
66
|
+
ar: `guides/${id}/ar.md`,
|
|
67
|
+
en: `guides/${id}/en.md`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const PORT = {
|
|
72
|
+
key: "game",
|
|
73
|
+
containerPort: 25_565,
|
|
74
|
+
protocol: "tcp",
|
|
75
|
+
hostRange: {
|
|
76
|
+
default: 25_565,
|
|
77
|
+
min: 25_565,
|
|
78
|
+
max: 25_664,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const gameManifestOf = (overrides: Record<string, unknown> = {}) => ({
|
|
83
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
84
|
+
kind: PackageKind.Game,
|
|
85
|
+
metadata: {
|
|
86
|
+
id: "minecraft-java",
|
|
87
|
+
name: {
|
|
88
|
+
ar: "ماينكرافت جافا",
|
|
89
|
+
en: "Minecraft Java",
|
|
90
|
+
},
|
|
91
|
+
description: {
|
|
92
|
+
ar: "سيرفر ماينكرافت جافا الأصلي.",
|
|
93
|
+
en: "Minecraft Java Edition, vanilla.",
|
|
94
|
+
},
|
|
95
|
+
tagline: {
|
|
96
|
+
ar: "سيرفرك جاهز خلال دقيقة",
|
|
97
|
+
en: "Your server ready in a minute",
|
|
98
|
+
},
|
|
99
|
+
category: GameCategory.Sandbox,
|
|
100
|
+
logo: "assets/logo.webp",
|
|
101
|
+
banner: "assets/banner.webp",
|
|
102
|
+
},
|
|
103
|
+
resources: {
|
|
104
|
+
minMemoryMb: 1024,
|
|
105
|
+
recommendedMemoryMb: 4096,
|
|
106
|
+
minDiskMb: 5120,
|
|
107
|
+
},
|
|
108
|
+
container: {
|
|
109
|
+
image: "serverkgg/minecraft-java",
|
|
110
|
+
ports: [
|
|
111
|
+
PORT,
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
guides: [
|
|
115
|
+
guideOf("getting-started"),
|
|
116
|
+
],
|
|
117
|
+
...overrides,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const botManifestOf = () => ({
|
|
121
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
122
|
+
kind: PackageKind.Bot,
|
|
123
|
+
metadata: {
|
|
124
|
+
id: "bot-node",
|
|
125
|
+
name: {
|
|
126
|
+
ar: "بوت نود",
|
|
127
|
+
en: "Node bot",
|
|
128
|
+
},
|
|
129
|
+
description: {
|
|
130
|
+
ar: "انشر بوتك من GitHub.",
|
|
131
|
+
en: "Deploy your bot from GitHub.",
|
|
132
|
+
},
|
|
133
|
+
tagline: {
|
|
134
|
+
ar: "أي بوت بـ Node.js",
|
|
135
|
+
en: "Any Node.js bot",
|
|
136
|
+
},
|
|
137
|
+
logo: "assets/logo.webp",
|
|
138
|
+
banner: "assets/banner.webp",
|
|
139
|
+
},
|
|
140
|
+
resources: {
|
|
141
|
+
minMemoryMb: 512,
|
|
142
|
+
recommendedMemoryMb: 1024,
|
|
143
|
+
minDiskMb: 2048,
|
|
144
|
+
},
|
|
145
|
+
container: {
|
|
146
|
+
image: "serverkgg/bot-node",
|
|
147
|
+
ports: [],
|
|
148
|
+
},
|
|
149
|
+
guides: [
|
|
150
|
+
guideOf("errors"),
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const guidePackageOf = () => ({
|
|
155
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
156
|
+
kind: PackageKind.Guide,
|
|
157
|
+
metadata: {
|
|
158
|
+
id: "platform",
|
|
159
|
+
},
|
|
160
|
+
guideTags: [
|
|
161
|
+
{
|
|
162
|
+
id: "start",
|
|
163
|
+
name: {
|
|
164
|
+
ar: "ابدأ من هنا",
|
|
165
|
+
en: "Start here",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "billing",
|
|
170
|
+
name: {
|
|
171
|
+
ar: "الفوترة",
|
|
172
|
+
en: "Billing",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
guides: [
|
|
177
|
+
guideOf("billing"),
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("renderReadme", () => {
|
|
182
|
+
let root = "";
|
|
183
|
+
|
|
184
|
+
const put = (path: string, content: string) => {
|
|
185
|
+
mkdirSync(dirname(join(root, path)), {
|
|
186
|
+
recursive: true,
|
|
187
|
+
});
|
|
188
|
+
writeFileSync(join(root, path), content);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const putPackage = (manifest: Record<string, unknown>, scripts: Record<string, string>) => {
|
|
192
|
+
put("serverk.yml", JSON.stringify(manifest));
|
|
193
|
+
put(
|
|
194
|
+
"package.json",
|
|
195
|
+
JSON.stringify({
|
|
196
|
+
name: "@serverkgg/package",
|
|
197
|
+
scripts,
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const render = () => {
|
|
203
|
+
const source = directorySource(root);
|
|
204
|
+
|
|
205
|
+
return renderReadme(source, readPackageDocument(source));
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
root = mkdtempSync(join(tmpdir(), "serverk-readme-"));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
afterEach(() => {
|
|
213
|
+
rmSync(root, {
|
|
214
|
+
force: true,
|
|
215
|
+
recursive: true,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("renders a game's banner, title, intro, sections and footer in order", () => {
|
|
220
|
+
putPackage(gameManifestOf(), FULL_SCRIPTS);
|
|
221
|
+
put(".githooks/pre-commit", HOOK);
|
|
222
|
+
put("serverk.schema.json", "{}");
|
|
223
|
+
put("src/driver.ts", "export const driver = {};\n");
|
|
224
|
+
put("image/Dockerfile", "FROM debian\n");
|
|
225
|
+
put("assets/logo.webp", "logo");
|
|
226
|
+
put("assets/banner.webp", "banner");
|
|
227
|
+
put("guides/getting-started/ar.md", "## البداية\n");
|
|
228
|
+
put("guides/getting-started/en.md", "## Start\n");
|
|
229
|
+
|
|
230
|
+
const readme = render();
|
|
231
|
+
const lines = readme.split("\n");
|
|
232
|
+
|
|
233
|
+
expect(lines.slice(0, 3)).toEqual([
|
|
234
|
+
"",
|
|
235
|
+
"",
|
|
236
|
+
"# ماينكرافت جافا (Minecraft Java)",
|
|
237
|
+
]);
|
|
238
|
+
expect(lines.filter((line) => line.startsWith("## "))).toEqual(HEADINGS);
|
|
239
|
+
expect(lines).toContain(
|
|
240
|
+
"يضم هذا الريبو كل ما تحتاجه منصة سيرفرك لتشغيل سيرفرات ماينكرافت جافا. سيرفر ماينكرافت جافا الأصلي. سيرفرك جاهز خلال دقيقة. صفحة اللعبة: [serverk.gg/games/minecraft-java](https://serverk.gg/games/minecraft-java)",
|
|
241
|
+
);
|
|
242
|
+
expect(lines).toContain(
|
|
243
|
+
"This repository holds everything Serverk needs to run Minecraft Java servers. Minecraft Java Edition, vanilla. Your server ready in a minute. Game page: [serverk.gg/games/minecraft-java](https://serverk.gg/games/minecraft-java)",
|
|
244
|
+
);
|
|
245
|
+
expect(lines.filter((line) => line.startsWith("- `"))).toEqual([
|
|
246
|
+
"- `serverk.yml`: the game manifest with its metadata, resources, ports, backup and reset rules, and the guide list.",
|
|
247
|
+
"- `serverk.schema.json`: the JSON Schema of `serverk.yml` for the editor, written by `serverk-bridge schema`.",
|
|
248
|
+
"- `src/`: the driver, built on `@serverkgg/bridge`; `src/driver.ts` exports it and `src/main.ts` serves it.",
|
|
249
|
+
"- `image/Dockerfile`: the container image, with the compiled bridge as its entrypoint.",
|
|
250
|
+
"- `assets/`: the logo and the banner the site and the panel show.",
|
|
251
|
+
"- `guides/<id>/ar.md` and `en.md`: the guide bodies; each guide's title, summary and tags live in `serverk.yml`.",
|
|
252
|
+
"- `bun install`: installs the dependencies, points Git at the hooks in `.githooks/` and writes `.claude/rules/package.md` from the bridge.",
|
|
253
|
+
"- `bun run check`: checks lint and formatting with Biome; `bun run fix` applies the fixes.",
|
|
254
|
+
"- `bun run tsc`: type-checks the code.",
|
|
255
|
+
"- `bun run test`: runs the tests.",
|
|
256
|
+
"- `bun run validate`: runs the checks the platform runs at publish, and fails while this README is out of date.",
|
|
257
|
+
"- `bun run compile`: builds `dist/bridge`, the linux/amd64 binary the image starts.",
|
|
258
|
+
]);
|
|
259
|
+
expect(lines).toContain(
|
|
260
|
+
"- [شرح getting-started · Guide getting-started](https://serverk.gg/games/minecraft-java/guides/getting-started)",
|
|
261
|
+
);
|
|
262
|
+
expect(lines).toContain(
|
|
263
|
+
"`.githooks/pre-commit` runs `bun run fix`, `bun run validate` and `bunx serverk-bridge schema --check` before every commit. There is no CI: a commit that passes the hook is ready.",
|
|
264
|
+
);
|
|
265
|
+
expect(lines).toContain(
|
|
266
|
+
"`bun install` runs `serverk-bridge link`, which links `@serverkgg/bridge` to a sibling `serverk` checkout at `../serverk/modules/bridge` when one exists. Never commit a `file:` or `link:` dependency.",
|
|
267
|
+
);
|
|
268
|
+
expect(readme).toContain("`bunx outdo package --prod --name minecraft-java`");
|
|
269
|
+
expect(readme).toContain(
|
|
270
|
+
"العربية هي لغة المصدر: يُكتب كل نص يقرؤه العميل بالعربية أولًا، بلغة رسمية مع لمسة خليجية خفيفة، ثم بالإنجليزية.",
|
|
271
|
+
);
|
|
272
|
+
expect(readme).toContain(
|
|
273
|
+
"Arabic is the source language: every string a customer reads is written in Arabic first, formal with a light Gulf touch, then in English.",
|
|
274
|
+
);
|
|
275
|
+
expect(readme).not.toContain(".claude/skills");
|
|
276
|
+
expect(lines).toContain(LICENSE_EN);
|
|
277
|
+
expect(lines.slice(-2)).toEqual([
|
|
278
|
+
FOOTER_EN,
|
|
279
|
+
"",
|
|
280
|
+
]);
|
|
281
|
+
expect(render()).toBe(readme);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("lists only the layout entries on disk and the scripts package.json declares", () => {
|
|
285
|
+
putPackage(
|
|
286
|
+
gameManifestOf({
|
|
287
|
+
guides: [],
|
|
288
|
+
}),
|
|
289
|
+
{
|
|
290
|
+
validate: "serverk-bridge validate",
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
put("assets/logo.webp", "logo");
|
|
294
|
+
put("assets/banner.webp", "banner");
|
|
295
|
+
put("image/detect.json", "{}");
|
|
296
|
+
|
|
297
|
+
const lines = render().split("\n");
|
|
298
|
+
|
|
299
|
+
expect(lines.filter((line) => line.startsWith("- `"))).toEqual([
|
|
300
|
+
"- `serverk.yml`: the game manifest with its metadata, resources, ports, backup and reset rules, and the guide list.",
|
|
301
|
+
"- `assets/`: the logo and the banner the site and the panel show.",
|
|
302
|
+
"- `bun install`: installs the dependencies.",
|
|
303
|
+
"- `bun run validate`: runs the checks the platform runs at publish, and fails while this README is out of date.",
|
|
304
|
+
]);
|
|
305
|
+
expect(lines.filter((line) => line.startsWith("## "))).toEqual([
|
|
306
|
+
"## Layout",
|
|
307
|
+
"## Develop",
|
|
308
|
+
"## Contribute",
|
|
309
|
+
"## License",
|
|
310
|
+
]);
|
|
311
|
+
expect(lines.some((line) => line.includes("pre-commit") || line.includes("serverk-bridge link"))).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("says nothing about CI when the repository has a workflow", () => {
|
|
315
|
+
putPackage(gameManifestOf(), FULL_SCRIPTS);
|
|
316
|
+
put(".githooks/pre-commit", HOOK);
|
|
317
|
+
put(".github/workflows/check.yml", "on: push\n");
|
|
318
|
+
|
|
319
|
+
expect(render()).toContain(
|
|
320
|
+
"`.githooks/pre-commit` runs `bun run fix`, `bun run validate` and `bunx serverk-bridge schema --check` before every commit.\n",
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("links a bot to the bots page and its guides, and lists its launcher and detection table", () => {
|
|
325
|
+
putPackage(botManifestOf(), FULL_SCRIPTS);
|
|
326
|
+
put("image/Dockerfile", "FROM node\n");
|
|
327
|
+
put("image/launcher.mjs", "");
|
|
328
|
+
put("image/detect.json", "{}");
|
|
329
|
+
put("templates/discord-node/index.js", "");
|
|
330
|
+
|
|
331
|
+
const lines = render().split("\n");
|
|
332
|
+
|
|
333
|
+
expect(lines.slice(0, 3)).toEqual([
|
|
334
|
+
"",
|
|
335
|
+
"",
|
|
336
|
+
"# بوت نود (Node bot)",
|
|
337
|
+
]);
|
|
338
|
+
expect(lines).toContain(
|
|
339
|
+
"يضم هذا الريبو بيئة تشغيل بوت نود، وهي كل ما تحتاجه منصة سيرفرك لتشغيل بوتك. انشر بوتك من GitHub. أي بوت بـ Node.js. صفحة البوتات: [serverk.gg/bots](https://serverk.gg/bots)",
|
|
340
|
+
);
|
|
341
|
+
expect(lines).toContain(
|
|
342
|
+
"This repository holds the Node bot runtime: everything Serverk needs to run your bot. Deploy your bot from GitHub. Any Node.js bot. Bots page: [serverk.gg/bots](https://serverk.gg/bots)",
|
|
343
|
+
);
|
|
344
|
+
expect(lines).toContain("- [شرح errors · Guide errors](https://serverk.gg/bots/bot-node/guides/errors)");
|
|
345
|
+
expect(lines).toContain(
|
|
346
|
+
"- `image/launcher.mjs`: what the bridge starts; it puts the deployed code in place, installs its dependencies and starts the bot.",
|
|
347
|
+
);
|
|
348
|
+
expect(lines).toContain(
|
|
349
|
+
"- `image/detect.json`: the start and install table the launcher reads, written by `serverk-bridge detect-table`.",
|
|
350
|
+
);
|
|
351
|
+
expect(lines).toContain(
|
|
352
|
+
"- `templates/`: starter bots, synced to the public `serverkgg/bot-template-*` repositories by `bunx outdo templates`.",
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("renders the guide package with its tags, its guides under /guides, the blog and its own release", () => {
|
|
357
|
+
putPackage(guidePackageOf(), {
|
|
358
|
+
validate: "serverk-bridge validate",
|
|
359
|
+
});
|
|
360
|
+
put("blog/first-server/ar.md", "---\n");
|
|
361
|
+
|
|
362
|
+
const readme = render();
|
|
363
|
+
const lines = readme.split("\n");
|
|
364
|
+
|
|
365
|
+
expect(lines[0]).toBe("# شروحات المنصة (Platform guides)");
|
|
366
|
+
expect(readme).not.toContain("banner");
|
|
367
|
+
expect(lines).toContain(
|
|
368
|
+
"يضم هذا الريبو الشروحات العامة لمنصة سيرفرك، وهي كل شرح لا يرتبط بلعبة معيّنة. وتتوزع على هذه الأقسام: «ابدأ من هنا»، و«الفوترة». صفحة الشروحات: [serverk.gg/guides](https://serverk.gg/guides)",
|
|
369
|
+
);
|
|
370
|
+
expect(lines).toContain(
|
|
371
|
+
'This repository holds the general guides for Serverk: every guide that is not tied to one game. They are grouped under "Start here" and "Billing". Guides page: [serverk.gg/guides](https://serverk.gg/guides)',
|
|
372
|
+
);
|
|
373
|
+
expect(lines).toContain("- [شرح billing · Guide billing](https://serverk.gg/guides/billing)");
|
|
374
|
+
expect(lines.some((line) => line.startsWith("- `blog/<id>/ar.md` and `en.md`: the blog articles"))).toBe(true);
|
|
375
|
+
expect(readme).toContain("`bunx outdo platform --prod`");
|
|
376
|
+
expect(readme).not.toContain("outdo package");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
for (const sibling of SIBLINGS) {
|
|
380
|
+
const dir = join(SIBLINGS_ROOT, sibling);
|
|
381
|
+
|
|
382
|
+
it.skipIf(!existsSync(join(dir, "serverk.yml")))(
|
|
383
|
+
`renders the ${sibling} readme from its own manifest, the same every time and under 90 lines`,
|
|
384
|
+
() => {
|
|
385
|
+
const source = directorySource(dir);
|
|
386
|
+
const document = readPackageDocument(source);
|
|
387
|
+
const readme = renderReadme(source, document);
|
|
388
|
+
const lines = readme.split("\n");
|
|
389
|
+
const name =
|
|
390
|
+
document.kind === PackageKind.Guide
|
|
391
|
+
? {
|
|
392
|
+
ar: "شروحات المنصة",
|
|
393
|
+
en: "Platform guides",
|
|
394
|
+
}
|
|
395
|
+
: document.metadata.name;
|
|
396
|
+
|
|
397
|
+
expect(lines).toContain(`# ${name.ar} (${name.en})`);
|
|
398
|
+
expect(lines.filter((line) => line.startsWith("## "))).toEqual(HEADINGS);
|
|
399
|
+
expect(lines.filter((line) => line.startsWith("- [")).length).toBe(document.guides.length);
|
|
400
|
+
|
|
401
|
+
for (const guide of document.guides) {
|
|
402
|
+
expect(readme).toContain(`/guides/${guide.id})`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
expect(lines.length).toBeLessThan(90);
|
|
406
|
+
expect(renderReadme(source, document)).toBe(readme);
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe("runReadme", () => {
|
|
413
|
+
let root = "";
|
|
414
|
+
let restore = () => {};
|
|
415
|
+
|
|
416
|
+
const target = () => {
|
|
417
|
+
return join(root, README_FILE);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const run = (...args: string[]) => {
|
|
421
|
+
return runReadme([
|
|
422
|
+
"--dir",
|
|
423
|
+
root,
|
|
424
|
+
...args,
|
|
425
|
+
]);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
beforeEach(() => {
|
|
429
|
+
root = mkdtempSync(join(tmpdir(), "serverk-readme-run-"));
|
|
430
|
+
writeFileSync(join(root, "serverk.yml"), JSON.stringify(gameManifestOf()));
|
|
431
|
+
|
|
432
|
+
const spy = spyOn(console, "log").mockImplementation(() => {});
|
|
433
|
+
|
|
434
|
+
restore = () => spy.mockRestore();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
afterEach(() => {
|
|
438
|
+
restore();
|
|
439
|
+
rmSync(root, {
|
|
440
|
+
force: true,
|
|
441
|
+
recursive: true,
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("writes README.md from the manifest, then passes its own check", async () => {
|
|
446
|
+
await run();
|
|
447
|
+
|
|
448
|
+
const source = directorySource(root);
|
|
449
|
+
|
|
450
|
+
expect(readFileSync(target(), "utf8")).toBe(renderReadme(source, readPackageDocument(source)));
|
|
451
|
+
|
|
452
|
+
await expect(run("--check")).resolves.toBeUndefined();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("check fails on a missing README and writes nothing", async () => {
|
|
456
|
+
await expect(run("--check")).rejects.toThrow(`${target()} is missing — run "serverk-bridge readme" and commit it`);
|
|
457
|
+
|
|
458
|
+
expect(existsSync(target())).toBe(false);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("check fails on a stale README and leaves it alone", async () => {
|
|
462
|
+
writeFileSync(target(), "# edited by hand\n");
|
|
463
|
+
|
|
464
|
+
await expect(run("--check")).rejects.toThrow(
|
|
465
|
+
`${target()} is out of date — run "serverk-bridge readme" and commit it`,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(readFileSync(target(), "utf8")).toBe("# edited by hand\n");
|
|
469
|
+
});
|
|
470
|
+
});
|