@serverkgg/bridge 0.1.2 → 0.1.3
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 -2
- package/rules/package.md +60 -0
- package/src/blog/articleFrontmatter.test.ts +10 -10
- package/src/blog/compile.test.ts +9 -9
- package/src/bot/botDeploy.test.ts +16 -16
- package/src/bot/botEnvironment.test.ts +8 -8
- package/src/bot/botIdentity.test.ts +12 -12
- package/src/bot/botPanel.test.ts +13 -13
- package/src/bot/botPanel.ts +4 -4
- package/src/bot/botState.test.ts +3 -3
- package/src/bot/botTerminal.test.ts +5 -5
- package/src/bot/botVariables.test.ts +8 -8
- package/src/bot/detect/botToken.test.ts +6 -6
- package/src/bot/detect/detect.test.ts +21 -21
- package/src/bot/detect/detectTable.test.ts +5 -5
- package/src/bot/detect/envExample.test.ts +2 -2
- package/src/bot/detect/envScan.test.ts +4 -4
- package/src/bot/detect/inferBotPlatform.test.ts +4 -4
- package/src/bot/detect/keys.test.ts +3 -3
- package/src/bot/detect/sensitiveKey.test.ts +5 -5
- package/src/bot/detect/serverkJson.test.ts +5 -5
- package/src/catalogs/curseforge.test.ts +35 -35
- package/src/catalogs/thunderstore.test.ts +9 -9
- package/src/cli/detectTable.test.ts +3 -3
- package/src/cli/link.test.ts +11 -11
- package/src/cli/main.ts +8 -0
- package/src/cli/rules.test.ts +198 -0
- package/src/cli/rules.ts +188 -0
- package/src/cli/validate.test.ts +52 -30
- package/src/cli/validate.ts +2 -0
- package/src/codecs/ini.test.ts +13 -13
- package/src/codecs/javaProperties.test.ts +11 -11
- package/src/codecs/sourceCfg.test.ts +14 -14
- package/src/codecs/xmlProperties.test.ts +10 -10
- package/src/core/bridgePort.test.ts +6 -6
- package/src/core/bridgeUserError.test.ts +4 -4
- package/src/core/bridgeValue.test.ts +5 -5
- package/src/core/describeDownloadFailure.test.ts +12 -12
- package/src/core/describeDownloadFailure.ts +10 -10
- package/src/guides/markdown.test.ts +31 -31
- package/src/install/installStamp.test.ts +9 -9
- package/src/manifest/extension.ts +36 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +208 -90
- package/src/manifest/manifest.ts +34 -0
- package/src/net/loopback.test.ts +10 -10
- package/src/presence/rosterSync.test.ts +7 -7
- package/src/probes/a2s.test.ts +5 -5
- package/src/probes/minecraftPing.test.ts +17 -17
- package/src/probes/raknetPing.test.ts +13 -13
- package/src/rcon/rconAccess.test.ts +20 -20
- package/src/rcon/rconAccess.ts +13 -13
- package/src/rcon/rconClient.test.ts +49 -49
- package/src/rcon/rconFrame.test.ts +14 -14
- package/src/rcon/rconIds.test.ts +4 -4
- package/src/rcon/rconWriter.test.ts +6 -6
- package/src/runtime/bridgeActivity.test.ts +15 -15
- package/src/runtime/bridgeBackupLease.test.ts +9 -9
- package/src/runtime/bridgeConfigReader.test.ts +5 -5
- package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
- package/src/runtime/bridgeContext.test.ts +5 -5
- package/src/runtime/bridgeDriver.test.ts +94 -94
- package/src/runtime/bridgeEvents.test.ts +15 -15
- package/src/runtime/bridgeFiles.test.ts +54 -54
- package/src/runtime/bridgeInstall.test.ts +10 -10
- package/src/runtime/bridgeLiveConfig.test.ts +6 -6
- package/src/runtime/bridgePending.test.ts +6 -6
- package/src/runtime/bridgeRedaction.test.ts +16 -16
- package/src/runtime/bridgeRegistry.test.ts +52 -52
- package/src/runtime/bridgeRegistry.ts +2 -2
- package/src/runtime/bridgeServer.test.ts +31 -31
- package/src/runtime/bridgeSetup.test.ts +27 -27
- package/src/runtime/bridgeSetup.ts +1 -1
- package/src/runtime/bridgeSetupHandlers.test.ts +13 -13
- package/src/runtime/bridgeSignal.test.ts +7 -7
- package/src/runtime/bridgeSupervisor.test.ts +42 -42
- package/src/runtime/serve.ts +4 -4
- package/src/steam/avatars.test.ts +13 -13
- package/src/steam/steamApp.test.ts +8 -8
- package/src/steam/steamcmd.test.ts +17 -17
- package/src/steam/steamcmd.ts +4 -4
- package/src/steam/steamcmdNarrator.test.ts +4 -4
- package/src/steam/workshop.test.ts +5 -5
- package/src/utils/changedSettingKeys.test.ts +5 -5
- package/src/utils/describeExecFailure.test.ts +4 -4
- package/src/utils/describeExecFailure.ts +1 -1
- package/src/utils/sameSettingValue.test.ts +7 -7
- package/src/utils/token.test.ts +7 -7
- package/src/wine/protonCommand.test.ts +5 -5
- package/src/wine/wineCommand.test.ts +6 -6
- package/src/wine/winePrefix.test.ts +6 -6
- package/src/wire/activity.test.ts +8 -8
- package/src/wire/panel.test.ts +28 -28
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
RULES_FILE,
|
|
7
|
+
readShippedRules,
|
|
8
|
+
rulesReportLines,
|
|
9
|
+
rulesState,
|
|
10
|
+
runRules,
|
|
11
|
+
SHIPPED_RULES_PATH,
|
|
12
|
+
writeRules,
|
|
13
|
+
} from "./rules";
|
|
14
|
+
|
|
15
|
+
const SHIPPED = "# Serverk package\n\nthe conventions\n";
|
|
16
|
+
|
|
17
|
+
describe("rules", () => {
|
|
18
|
+
let root = "";
|
|
19
|
+
let target = "";
|
|
20
|
+
|
|
21
|
+
const writeTarget = (text: string) => {
|
|
22
|
+
mkdirSync(dirname(target), {
|
|
23
|
+
recursive: true,
|
|
24
|
+
});
|
|
25
|
+
writeFileSync(target, text);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
root = mkdtempSync(`${tmpdir()}/serverk-rules-`);
|
|
30
|
+
target = join(root, RULES_FILE);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
rmSync(root, {
|
|
35
|
+
force: true,
|
|
36
|
+
recursive: true,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("writes the file and its folders when it is missing", () => {
|
|
41
|
+
expect(writeRules(root, SHIPPED)).toEqual({
|
|
42
|
+
kind: "written",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(readFileSync(target, "utf8")).toBe(SHIPPED);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("rewrites a file that is out of date", () => {
|
|
49
|
+
writeTarget("# edited by hand\n");
|
|
50
|
+
|
|
51
|
+
expect(writeRules(root, SHIPPED)).toEqual({
|
|
52
|
+
kind: "written",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(readFileSync(target, "utf8")).toBe(SHIPPED);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("leaves an identical file untouched", () => {
|
|
59
|
+
writeTarget(SHIPPED);
|
|
60
|
+
|
|
61
|
+
const before = statSync(target).mtimeMs;
|
|
62
|
+
|
|
63
|
+
expect(writeRules(root, SHIPPED)).toEqual({
|
|
64
|
+
kind: "unchanged",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(statSync(target).mtimeMs).toBe(before);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("reports a target it cannot write instead of throwing", () => {
|
|
71
|
+
writeFileSync(join(root, ".claude"), "a file where the folder should be");
|
|
72
|
+
|
|
73
|
+
const result = writeRules(root, SHIPPED);
|
|
74
|
+
|
|
75
|
+
expect(result.kind).toBe("failed");
|
|
76
|
+
expect(existsSync(target)).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("names the state of the file on disk", () => {
|
|
80
|
+
expect(rulesState(root, SHIPPED)).toBe("missing");
|
|
81
|
+
|
|
82
|
+
writeTarget("# stale\n");
|
|
83
|
+
|
|
84
|
+
expect(rulesState(root, SHIPPED)).toBe("stale");
|
|
85
|
+
|
|
86
|
+
writeTarget(SHIPPED);
|
|
87
|
+
|
|
88
|
+
expect(rulesState(root, SHIPPED)).toBe("current");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("writes the shipped rules when run", async () => {
|
|
92
|
+
await runRules([
|
|
93
|
+
"--dir",
|
|
94
|
+
root,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
expect(readFileSync(target, "utf8")).toBe(readShippedRules());
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("succeeds without writing when the target cannot be written", async () => {
|
|
101
|
+
writeFileSync(join(root, ".claude"), "a file where the folder should be");
|
|
102
|
+
|
|
103
|
+
await expect(
|
|
104
|
+
runRules([
|
|
105
|
+
"--dir",
|
|
106
|
+
root,
|
|
107
|
+
]),
|
|
108
|
+
).resolves.toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("check fails when the file is missing and writes nothing", async () => {
|
|
112
|
+
await expect(
|
|
113
|
+
runRules([
|
|
114
|
+
"--dir",
|
|
115
|
+
root,
|
|
116
|
+
"--check",
|
|
117
|
+
]),
|
|
118
|
+
).rejects.toThrow(`${RULES_FILE} is missing — run "serverk-bridge rules"`);
|
|
119
|
+
|
|
120
|
+
expect(existsSync(target)).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("check fails when the file is out of date and leaves it alone", async () => {
|
|
124
|
+
writeTarget("# stale\n");
|
|
125
|
+
|
|
126
|
+
await expect(
|
|
127
|
+
runRules([
|
|
128
|
+
"--dir",
|
|
129
|
+
root,
|
|
130
|
+
"--check",
|
|
131
|
+
]),
|
|
132
|
+
).rejects.toThrow(`${RULES_FILE} is out of date — run "serverk-bridge rules"`);
|
|
133
|
+
|
|
134
|
+
expect(readFileSync(target, "utf8")).toBe("# stale\n");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("check passes when the file matches the shipped rules", async () => {
|
|
138
|
+
writeTarget(readShippedRules());
|
|
139
|
+
|
|
140
|
+
await expect(
|
|
141
|
+
runRules([
|
|
142
|
+
"--dir",
|
|
143
|
+
root,
|
|
144
|
+
"--check",
|
|
145
|
+
]),
|
|
146
|
+
).resolves.toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("validate warns about a missing or stale file and says nothing about a current one", () => {
|
|
150
|
+
expect(rulesReportLines(root)).toEqual([
|
|
151
|
+
` warning: ${RULES_FILE} is missing — run "serverk-bridge rules"`,
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
writeTarget("# stale\n");
|
|
155
|
+
|
|
156
|
+
expect(rulesReportLines(root)).toEqual([
|
|
157
|
+
` warning: ${RULES_FILE} is out of date — run "serverk-bridge rules"`,
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
writeTarget(readShippedRules());
|
|
161
|
+
|
|
162
|
+
expect(rulesReportLines(root)).toEqual([]);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("the shipped rules", () => {
|
|
167
|
+
const packageRoot = join(dirname(SHIPPED_RULES_PATH), "..");
|
|
168
|
+
|
|
169
|
+
it("sit at rules/package.md beside the bridge's package.json", () => {
|
|
170
|
+
expect(SHIPPED_RULES_PATH).toBe(join(packageRoot, "rules/package.md"));
|
|
171
|
+
expect(existsSync(join(packageRoot, "package.json"))).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("are published with the module", () => {
|
|
175
|
+
const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
176
|
+
|
|
177
|
+
expect(manifest.name).toBe("@serverkgg/bridge");
|
|
178
|
+
expect(manifest.files).toContain("rules");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("always load, with no paths frontmatter", () => {
|
|
182
|
+
const text = readShippedRules();
|
|
183
|
+
|
|
184
|
+
expect(text.startsWith("---")).toBe(false);
|
|
185
|
+
expect(text).not.toMatch(/^paths:/m);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("send the reader to the project instructions in ../serverk/CLAUDE.md", () => {
|
|
189
|
+
expect(readShippedRules()).toContain("../serverk/CLAUDE.md");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("say they are generated and must not be edited in place", () => {
|
|
193
|
+
const opening = readShippedRules().split("\n").slice(0, 3).join("\n");
|
|
194
|
+
|
|
195
|
+
expect(opening).toContain("serverk-bridge rules");
|
|
196
|
+
expect(opening).toContain("Do not edit it here");
|
|
197
|
+
});
|
|
198
|
+
});
|
package/src/cli/rules.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { errorText } from "../runtime/bridgeLog";
|
|
6
|
+
|
|
7
|
+
export const RULES_FILE = ".claude/rules/package.md";
|
|
8
|
+
|
|
9
|
+
export const SHIPPED_RULES_PATH = fileURLToPath(new URL("../../rules/package.md", import.meta.url));
|
|
10
|
+
|
|
11
|
+
const RULES_COMMAND = "serverk-bridge rules";
|
|
12
|
+
|
|
13
|
+
const HELP = `serverk-bridge rules [options]
|
|
14
|
+
|
|
15
|
+
write ${RULES_FILE}, the conventions for package code, from the bridge this package pins.
|
|
16
|
+
the file is generated on every install, so it is gitignored and never edited in place.
|
|
17
|
+
a write that fails is reported and never fails the install.
|
|
18
|
+
|
|
19
|
+
options:
|
|
20
|
+
--dir <path> package directory (default: current directory)
|
|
21
|
+
--check change nothing, fail when the file is missing or out of date
|
|
22
|
+
--help show this help`;
|
|
23
|
+
|
|
24
|
+
export type RulesState = "current" | "missing" | "stale";
|
|
25
|
+
|
|
26
|
+
export type RulesResult =
|
|
27
|
+
| {
|
|
28
|
+
kind: "unchanged";
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
kind: "written";
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
kind: "failed";
|
|
35
|
+
reason: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const readShippedRules = () => {
|
|
39
|
+
return readFileSync(SHIPPED_RULES_PATH, "utf8");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const rulesPath = (dir: string) => {
|
|
43
|
+
return join(resolve(dir), RULES_FILE);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const readCurrent = (dir: string) => {
|
|
47
|
+
try {
|
|
48
|
+
return readFileSync(rulesPath(dir), "utf8");
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const rulesState = (dir: string, shipped: string): RulesState => {
|
|
55
|
+
const current = readCurrent(dir);
|
|
56
|
+
|
|
57
|
+
if (current === null) {
|
|
58
|
+
return "missing";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return current === shipped ? "current" : "stale";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const writeRules = (dir: string, shipped: string): RulesResult => {
|
|
65
|
+
if (rulesState(dir, shipped) === "current") {
|
|
66
|
+
return {
|
|
67
|
+
kind: "unchanged",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const target = rulesPath(dir);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
mkdirSync(dirname(target), {
|
|
75
|
+
recursive: true,
|
|
76
|
+
});
|
|
77
|
+
writeFileSync(target, shipped);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "failed",
|
|
81
|
+
reason: errorText(error),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
kind: "written",
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const rulesReportLines = (dir: string): string[] => {
|
|
91
|
+
let shipped: string;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
shipped = readShippedRules();
|
|
95
|
+
} catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const state = rulesState(dir, shipped);
|
|
100
|
+
|
|
101
|
+
if (state === "current") {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
` warning: ${RULES_FILE} is ${state === "missing" ? "missing" : "out of date"} — run "${RULES_COMMAND}"`,
|
|
107
|
+
];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const checkRules = (dir: string) => {
|
|
111
|
+
const state = rulesState(dir, readShippedRules());
|
|
112
|
+
|
|
113
|
+
if (state === "missing") {
|
|
114
|
+
throw new Error(`${RULES_FILE} is missing — run "${RULES_COMMAND}"`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (state === "stale") {
|
|
118
|
+
throw new Error(`${RULES_FILE} is out of date — run "${RULES_COMMAND}"`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(`${RULES_FILE} is up to date`);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const applyRules = (dir: string) => {
|
|
125
|
+
let shipped: string;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
shipped = readShippedRules();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`skipped ${RULES_FILE}: the bridge ships no rules (${errorText(error)})`);
|
|
131
|
+
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = writeRules(dir, shipped);
|
|
136
|
+
|
|
137
|
+
switch (result.kind) {
|
|
138
|
+
case "unchanged": {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case "written": {
|
|
143
|
+
console.log(`wrote ${RULES_FILE}`);
|
|
144
|
+
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case "failed": {
|
|
149
|
+
console.error(`could not write ${RULES_FILE}: ${result.reason}`);
|
|
150
|
+
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const runRules = async (args: string[]) => {
|
|
157
|
+
const { values } = parseArgs({
|
|
158
|
+
args,
|
|
159
|
+
options: {
|
|
160
|
+
dir: {
|
|
161
|
+
type: "string",
|
|
162
|
+
default: ".",
|
|
163
|
+
},
|
|
164
|
+
check: {
|
|
165
|
+
type: "boolean",
|
|
166
|
+
default: false,
|
|
167
|
+
},
|
|
168
|
+
help: {
|
|
169
|
+
type: "boolean",
|
|
170
|
+
default: false,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (values.help) {
|
|
176
|
+
console.log(HELP);
|
|
177
|
+
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (values.check) {
|
|
182
|
+
checkRules(values.dir);
|
|
183
|
+
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
applyRules(values.dir);
|
|
188
|
+
};
|
package/src/cli/validate.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
|
|
2
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type BridgeTab,
|
|
17
17
|
type BridgeTableSection,
|
|
18
18
|
} from "../panel";
|
|
19
|
+
import { RULES_FILE, readShippedRules, writeRules } from "./rules";
|
|
19
20
|
import { availabilityReportLines, placementReportLines, runValidate, setupReportLines } from "./validate";
|
|
20
21
|
|
|
21
22
|
const text = (value: string) => ({
|
|
@@ -54,7 +55,7 @@ describe("validate", () => {
|
|
|
54
55
|
});
|
|
55
56
|
});
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
it("refuses a file: bridge dependency", async () => {
|
|
58
59
|
writePackage({
|
|
59
60
|
"@serverkgg/bridge": "file:../serverk/modules/bridge",
|
|
60
61
|
});
|
|
@@ -64,7 +65,7 @@ describe("validate", () => {
|
|
|
64
65
|
);
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
it("refuses a link: bridge dependency", async () => {
|
|
68
69
|
writePackage({
|
|
69
70
|
"@serverkgg/bridge": "link:@serverkgg/bridge",
|
|
70
71
|
});
|
|
@@ -72,7 +73,7 @@ describe("validate", () => {
|
|
|
72
73
|
await expect(validate()).rejects.toThrow('depends on @serverkgg/bridge via "link:@serverkgg/bridge"');
|
|
73
74
|
});
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
it("refuses a file: bridge devDependency", async () => {
|
|
76
77
|
writeFileSync(
|
|
77
78
|
join(root, "package.json"),
|
|
78
79
|
JSON.stringify({
|
|
@@ -86,7 +87,7 @@ describe("validate", () => {
|
|
|
86
87
|
await expect(validate()).rejects.toThrow("pin a published version");
|
|
87
88
|
});
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
it("accepts a published bridge dependency and moves on to the manifest", async () => {
|
|
90
91
|
writePackage({
|
|
91
92
|
"@serverkgg/bridge": "0.0.3",
|
|
92
93
|
});
|
|
@@ -94,7 +95,7 @@ describe("validate", () => {
|
|
|
94
95
|
await expect(validate()).rejects.toThrow("missing serverk.yml");
|
|
95
96
|
});
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
it("ignores a package without a bridge dependency", async () => {
|
|
98
99
|
writePackage({
|
|
99
100
|
zod: "4.4.3",
|
|
100
101
|
});
|
|
@@ -102,19 +103,19 @@ describe("validate", () => {
|
|
|
102
103
|
await expect(validate()).rejects.toThrow("missing serverk.yml");
|
|
103
104
|
});
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
it("ignores a package.json that is absent", async () => {
|
|
106
107
|
await expect(validate()).rejects.toThrow("missing serverk.yml");
|
|
107
108
|
});
|
|
108
109
|
});
|
|
109
110
|
|
|
110
111
|
describe("the setup report", () => {
|
|
111
|
-
|
|
112
|
+
it("says none for a driver that declares no setup", () => {
|
|
112
113
|
expect(setupReportLines(undefined)).toEqual([
|
|
113
114
|
" setup: none",
|
|
114
115
|
]);
|
|
115
116
|
});
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
it("names every step, its kind, whether it is required, and where it lands", () => {
|
|
118
119
|
const setup: BridgeSetupModule = {
|
|
119
120
|
kind: BridgeKind.Setup,
|
|
120
121
|
steps: [
|
|
@@ -138,8 +139,8 @@ describe("the setup report", () => {
|
|
|
138
139
|
},
|
|
139
140
|
{
|
|
140
141
|
kind: BridgeSetupStepKind.Open,
|
|
141
|
-
id: "
|
|
142
|
-
title: text("
|
|
142
|
+
id: "dashboard",
|
|
143
|
+
title: text("Dashboard"),
|
|
143
144
|
required: false,
|
|
144
145
|
target: {
|
|
145
146
|
tab: GuideOpenTab.Panel,
|
|
@@ -165,12 +166,12 @@ describe("the setup report", () => {
|
|
|
165
166
|
" setup: 4 step(s)",
|
|
166
167
|
" license — form, required → license/license [LICENSE_KEY]",
|
|
167
168
|
" verify — driver, required → driver (needs the game running)",
|
|
168
|
-
'
|
|
169
|
+
' dashboard — open, optional → panel tab "license"',
|
|
169
170
|
" invite — open, optional → access",
|
|
170
171
|
]);
|
|
171
172
|
});
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
it("leaves a driver step that needs nothing running with no target", () => {
|
|
174
175
|
expect(
|
|
175
176
|
setupReportLines({
|
|
176
177
|
kind: BridgeKind.Setup,
|
|
@@ -192,7 +193,7 @@ describe("the setup report", () => {
|
|
|
192
193
|
]);
|
|
193
194
|
});
|
|
194
195
|
|
|
195
|
-
|
|
196
|
+
it("reads an omitted required as required, the way the platform does", () => {
|
|
196
197
|
expect(
|
|
197
198
|
setupReportLines({
|
|
198
199
|
kind: BridgeKind.Setup,
|
|
@@ -259,7 +260,7 @@ describe("the placement report", () => {
|
|
|
259
260
|
},
|
|
260
261
|
});
|
|
261
262
|
|
|
262
|
-
|
|
263
|
+
it("says nothing about a roster table that declares where it belongs", () => {
|
|
263
264
|
expect(
|
|
264
265
|
placementReportLines(
|
|
265
266
|
driverWithTabs([
|
|
@@ -277,7 +278,7 @@ describe("the placement report", () => {
|
|
|
277
278
|
]);
|
|
278
279
|
});
|
|
279
280
|
|
|
280
|
-
|
|
281
|
+
it("warns that the platform absorbs an unplaced players collection by convention", () => {
|
|
281
282
|
expect(
|
|
282
283
|
placementReportLines(
|
|
283
284
|
driverWithTabs([
|
|
@@ -295,7 +296,7 @@ describe("the placement report", () => {
|
|
|
295
296
|
]);
|
|
296
297
|
});
|
|
297
298
|
|
|
298
|
-
|
|
299
|
+
it("warns about a tab that takes the name of a platform sidebar entry", () => {
|
|
299
300
|
expect(
|
|
300
301
|
placementReportLines(
|
|
301
302
|
driverWithTabs([
|
|
@@ -313,7 +314,7 @@ describe("the placement report", () => {
|
|
|
313
314
|
]);
|
|
314
315
|
});
|
|
315
316
|
|
|
316
|
-
|
|
317
|
+
it("leaves a players tab alone while it is the roster tab", () => {
|
|
317
318
|
expect(
|
|
318
319
|
placementReportLines(
|
|
319
320
|
driverWithTabs([
|
|
@@ -331,7 +332,7 @@ describe("the placement report", () => {
|
|
|
331
332
|
]);
|
|
332
333
|
});
|
|
333
334
|
|
|
334
|
-
|
|
335
|
+
it("warns about a players tab that holds no roster", () => {
|
|
335
336
|
expect(
|
|
336
337
|
placementReportLines(
|
|
337
338
|
driverWithTabs([
|
|
@@ -349,7 +350,7 @@ describe("the placement report", () => {
|
|
|
349
350
|
]);
|
|
350
351
|
});
|
|
351
352
|
|
|
352
|
-
|
|
353
|
+
it("says nothing about a tab that declares no sections at all", () => {
|
|
353
354
|
expect(
|
|
354
355
|
placementReportLines(
|
|
355
356
|
driverWithTabs([
|
|
@@ -363,7 +364,7 @@ describe("the placement report", () => {
|
|
|
363
364
|
).toEqual([]);
|
|
364
365
|
});
|
|
365
366
|
|
|
366
|
-
|
|
367
|
+
it("still warns about an empty players tab, which holds no roster", () => {
|
|
367
368
|
expect(
|
|
368
369
|
placementReportLines(
|
|
369
370
|
driverWithTabs([
|
|
@@ -379,7 +380,7 @@ describe("the placement report", () => {
|
|
|
379
380
|
]);
|
|
380
381
|
});
|
|
381
382
|
|
|
382
|
-
|
|
383
|
+
it("says nothing about a tab that keeps a leftover section", () => {
|
|
383
384
|
expect(
|
|
384
385
|
placementReportLines(
|
|
385
386
|
driverWithTabs([
|
|
@@ -493,6 +494,7 @@ describe("validating a bot package", () => {
|
|
|
493
494
|
writeFileSync(join(root, "assets/logo.webp"), webp(256, 256));
|
|
494
495
|
writeFileSync(join(root, "assets/banner.webp"), webp(1280, 720));
|
|
495
496
|
writeFileSync(join(root, "src/driver.ts"), BOT_DRIVER);
|
|
497
|
+
writeRules(root, readShippedRules());
|
|
496
498
|
};
|
|
497
499
|
|
|
498
500
|
const validate = () => {
|
|
@@ -521,7 +523,7 @@ describe("validating a bot package", () => {
|
|
|
521
523
|
});
|
|
522
524
|
});
|
|
523
525
|
|
|
524
|
-
|
|
526
|
+
it("validates a bot with no ports and says which kind it read", async () => {
|
|
525
527
|
writeBot(botManifest());
|
|
526
528
|
|
|
527
529
|
await validate();
|
|
@@ -535,7 +537,27 @@ describe("validating a bot package", () => {
|
|
|
535
537
|
expect(lines.some((line) => line.includes("warning"))).toBe(false);
|
|
536
538
|
});
|
|
537
539
|
|
|
538
|
-
|
|
540
|
+
it("warns about a missing rule file and still validates", async () => {
|
|
541
|
+
writeBot(botManifest());
|
|
542
|
+
rmSync(join(root, RULES_FILE));
|
|
543
|
+
|
|
544
|
+
await validate();
|
|
545
|
+
|
|
546
|
+
expect(lines).toContain("bot-node is valid");
|
|
547
|
+
expect(lines).toContain(` warning: ${RULES_FILE} is missing — run "serverk-bridge rules"`);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("warns about a stale rule file and still validates", async () => {
|
|
551
|
+
writeBot(botManifest());
|
|
552
|
+
writeFileSync(join(root, RULES_FILE), "# edited by hand\n");
|
|
553
|
+
|
|
554
|
+
await validate();
|
|
555
|
+
|
|
556
|
+
expect(lines).toContain("bot-node is valid");
|
|
557
|
+
expect(lines).toContain(` warning: ${RULES_FILE} is out of date — run "serverk-bridge rules"`);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("says when a bot turns the environment vault on", async () => {
|
|
539
561
|
writeBot(
|
|
540
562
|
botManifest({
|
|
541
563
|
environment: {
|
|
@@ -549,7 +571,7 @@ describe("validating a bot package", () => {
|
|
|
549
571
|
expect(lines).toContain(" environment: vault on");
|
|
550
572
|
});
|
|
551
573
|
|
|
552
|
-
|
|
574
|
+
it("fails a bot whose detection table is missing", async () => {
|
|
553
575
|
writeBot(botManifest(), null);
|
|
554
576
|
|
|
555
577
|
await expect(validate()).rejects.toThrow(
|
|
@@ -557,7 +579,7 @@ describe("validating a bot package", () => {
|
|
|
557
579
|
);
|
|
558
580
|
});
|
|
559
581
|
|
|
560
|
-
|
|
582
|
+
it("fails a bot whose detection table is stale", async () => {
|
|
561
583
|
writeBot(botManifest(), "{}\n");
|
|
562
584
|
|
|
563
585
|
await expect(validate()).rejects.toThrow(
|
|
@@ -565,7 +587,7 @@ describe("validating a bot package", () => {
|
|
|
565
587
|
);
|
|
566
588
|
});
|
|
567
589
|
|
|
568
|
-
|
|
590
|
+
it("warns about a port on a bot but still validates it", async () => {
|
|
569
591
|
writeBot(
|
|
570
592
|
botManifest({
|
|
571
593
|
container: {
|
|
@@ -585,7 +607,7 @@ describe("validating a bot package", () => {
|
|
|
585
607
|
);
|
|
586
608
|
});
|
|
587
609
|
|
|
588
|
-
|
|
610
|
+
it("fails a bot that declares platform secrets", async () => {
|
|
589
611
|
writeBot(
|
|
590
612
|
botManifest({
|
|
591
613
|
secrets: [
|
|
@@ -620,7 +642,7 @@ describe("availability report", () => {
|
|
|
620
642
|
modules,
|
|
621
643
|
});
|
|
622
644
|
|
|
623
|
-
|
|
645
|
+
it("warns about a protected module that cannot say when its action is unavailable", () => {
|
|
624
646
|
expect(
|
|
625
647
|
availabilityReportLines(
|
|
626
648
|
driverWithModules({
|
|
@@ -643,7 +665,7 @@ describe("availability report", () => {
|
|
|
643
665
|
]);
|
|
644
666
|
});
|
|
645
667
|
|
|
646
|
-
|
|
668
|
+
it("says nothing when the protected module declares the hook or protects nothing", () => {
|
|
647
669
|
expect(
|
|
648
670
|
availabilityReportLines(
|
|
649
671
|
driverWithModules({
|
package/src/cli/validate.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../protocol";
|
|
32
32
|
import { validateDriver, validatePortActivation } from "../runtime/bridgeDriver";
|
|
33
33
|
import { directorySource } from "./directorySource";
|
|
34
|
+
import { rulesReportLines } from "./rules";
|
|
34
35
|
|
|
35
36
|
const HELP = `serverk-bridge validate [options]
|
|
36
37
|
|
|
@@ -446,6 +447,7 @@ const validateRunnablePackage = async (
|
|
|
446
447
|
...botPortLines(manifest),
|
|
447
448
|
...botEnvironmentLines(manifest),
|
|
448
449
|
...detectLines,
|
|
450
|
+
...rulesReportLines(dir),
|
|
449
451
|
]) {
|
|
450
452
|
console.log(line);
|
|
451
453
|
}
|