@serverkgg/bridge 0.1.2 → 0.1.4
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 +65 -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 +179 -31
- package/src/cli/validate.ts +44 -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/protocol/bridgePreview.ts +1 -0
- package/src/rcon/rconAccess.test.ts +20 -20
- package/src/rcon/rconAccess.ts +14 -14
- 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 +84 -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
|
+
};
|