@serverkgg/bridge 0.0.21 → 0.1.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 +3 -1
- package/src/bot/botDeploy.test.ts +363 -0
- package/src/bot/botDeploy.ts +136 -0
- package/src/bot/botEnvironment.test.ts +189 -0
- package/src/bot/botEnvironment.ts +141 -0
- package/src/bot/botIdentity.test.ts +251 -0
- package/src/bot/botIdentity.ts +140 -0
- package/src/bot/botPanel.test.ts +246 -0
- package/src/bot/botPanel.ts +129 -0
- package/src/bot/botState.test.ts +45 -0
- package/src/bot/botState.ts +54 -0
- package/src/bot/botTerminal.test.ts +79 -0
- package/src/bot/botTerminal.ts +62 -0
- package/src/bot/botVariables.test.ts +193 -0
- package/src/bot/botVariables.ts +73 -0
- package/src/bot/detect/botFramework.ts +23 -0
- package/src/bot/detect/botFrameworks.ts +198 -0
- package/src/bot/detect/botInspectWarning.ts +15 -0
- package/src/bot/detect/botPackageManager.ts +6 -0
- package/src/bot/detect/botPlatform.ts +10 -0
- package/src/bot/detect/botRuntime.ts +15 -0
- package/src/bot/detect/botToken.test.ts +71 -0
- package/src/bot/detect/botToken.ts +126 -0
- package/src/bot/detect/detect.test.ts +543 -0
- package/src/bot/detect/detect.ts +842 -0
- package/src/bot/detect/detectResult.ts +40 -0
- package/src/bot/detect/detectTable.test.ts +72 -0
- package/src/bot/detect/detectTable.ts +166 -0
- package/src/bot/detect/envExample.test.ts +29 -0
- package/src/bot/detect/envExample.ts +48 -0
- package/src/bot/detect/envKey.ts +7 -0
- package/src/bot/detect/envScan.test.ts +39 -0
- package/src/bot/detect/envScan.ts +87 -0
- package/src/bot/detect/index.ts +17 -0
- package/src/bot/detect/inferBotPlatform.test.ts +60 -0
- package/src/bot/detect/inferBotPlatform.ts +28 -0
- package/src/bot/detect/keys.test.ts +28 -0
- package/src/bot/detect/keys.ts +64 -0
- package/src/bot/detect/sensitiveKey.test.ts +91 -0
- package/src/bot/detect/sensitiveKey.ts +44 -0
- package/src/bot/detect/serverkJson.test.ts +88 -0
- package/src/bot/detect/serverkJson.ts +174 -0
- package/src/bot/index.ts +8 -0
- package/src/cli/detectTable.test.ts +66 -0
- package/src/cli/detectTable.ts +43 -0
- package/src/cli/main.ts +12 -4
- package/src/cli/renderedFile.ts +35 -0
- package/src/cli/schema.ts +7 -26
- package/src/cli/validate.test.ts +283 -3
- package/src/cli/validate.ts +105 -7
- package/src/context/bridgeContext.ts +17 -1
- package/src/core/bridgeAction.ts +2 -0
- package/src/core/bridgeActivity.ts +9 -0
- package/src/core/bridgeAvailability.ts +10 -0
- package/src/core/bridgePort.test.ts +45 -0
- package/src/core/bridgePort.ts +26 -0
- package/src/core/bridgeUserError.test.ts +10 -0
- package/src/core/bridgeUserError.ts +6 -2
- package/src/core/describeDownloadFailure.test.ts +58 -0
- package/src/core/describeDownloadFailure.ts +69 -0
- package/src/core/index.ts +3 -0
- package/src/guides/compile.ts +2 -2
- package/src/manifest/index.ts +1 -0
- package/src/manifest/kind.ts +5 -0
- package/src/manifest/manifest.test.ts +630 -3
- package/src/manifest/manifest.ts +391 -87
- package/src/manifest/runtime.ts +2 -2
- package/src/manifest/source.ts +4 -4
- package/src/modules/bridge.ts +8 -1
- package/src/modules/bridgeLifecycleSubject.ts +8 -0
- package/src/modules/bridgeModule.ts +19 -1
- package/src/modules/bridgeStopSignal.ts +13 -0
- package/src/modules/index.ts +2 -0
- package/src/panel/bridgeDetail.ts +4 -1
- package/src/protocol/bridgeActivity.ts +54 -0
- package/src/protocol/bridgeConfig.ts +2 -0
- package/src/protocol/bridgeEvents.ts +30 -0
- package/src/protocol/bridgeFrame.ts +10 -1
- package/src/protocol/index.ts +1 -0
- package/src/runtime/bridgeActions.ts +19 -2
- package/src/runtime/bridgeActivity.test.ts +318 -0
- package/src/runtime/bridgeActivity.ts +268 -0
- package/src/runtime/bridgeConfigReader.test.ts +80 -0
- package/src/runtime/bridgeConfigReader.ts +20 -0
- package/src/runtime/bridgeContext.test.ts +247 -0
- package/src/runtime/bridgeContext.ts +43 -10
- package/src/runtime/bridgeDriver.test.ts +153 -1
- package/src/runtime/bridgeDriver.ts +64 -8
- package/src/runtime/bridgeFiles.test.ts +62 -0
- package/src/runtime/bridgeFiles.ts +12 -2
- package/src/runtime/bridgeLiveConfig.ts +18 -0
- package/src/runtime/bridgeLog.ts +10 -0
- package/src/runtime/bridgeRedaction.test.ts +410 -0
- package/src/runtime/bridgeRedaction.ts +214 -0
- package/src/runtime/bridgeRefusalError.ts +13 -0
- package/src/runtime/bridgeRegistry.test.ts +1056 -10
- package/src/runtime/bridgeRegistry.ts +464 -50
- package/src/runtime/bridgeScopedContext.ts +55 -0
- package/src/runtime/bridgeServer.test.ts +534 -4
- package/src/runtime/bridgeServer.ts +145 -38
- package/src/runtime/bridgeSetupHandlers.test.ts +156 -5
- package/src/runtime/bridgeSetupHandlers.ts +59 -14
- package/src/runtime/bridgeSupervisor.test.ts +670 -2
- package/src/runtime/bridgeSupervisor.ts +219 -67
- package/src/runtime/serve.ts +87 -11
- package/src/steam/steamcmd.test.ts +32 -1
- package/src/steam/steamcmd.ts +27 -4
- package/src/utils/changedSettingKeys.test.ts +65 -0
- package/src/utils/changedSettingKeys.ts +6 -0
- package/src/utils/describeExecFailure.test.ts +34 -0
- package/src/utils/describeExecFailure.ts +9 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/sameSettingValue.test.ts +35 -0
- package/src/utils/sameSettingValue.ts +40 -0
- package/src/wire/activity.test.ts +95 -0
- package/src/wire/activity.ts +41 -0
- package/src/wire/index.ts +1 -0
- package/src/wire/panel.test.ts +67 -1
- package/src/wire/panel.ts +11 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BridgeContext } from "../context";
|
|
2
|
-
import type { BridgeValues } from "../core";
|
|
2
|
+
import type { BridgeActivity, BridgeValues } from "../core";
|
|
3
3
|
import type { BridgeConfig, BridgeSetupRuntime } from "../protocol";
|
|
4
4
|
import { stripAnsi } from "../utils";
|
|
5
5
|
import { createBridgeCodecs } from "./bridgeCodec";
|
|
@@ -7,7 +7,7 @@ import { configPath } from "./bridgeConfigReader";
|
|
|
7
7
|
import { createBridgeEgress } from "./bridgeEgress";
|
|
8
8
|
import { createBridgeExec } from "./bridgeExec";
|
|
9
9
|
import { createBridgeFiles } from "./bridgeFiles";
|
|
10
|
-
import { createBridgeLiveConfig } from "./bridgeLiveConfig";
|
|
10
|
+
import { type BridgeLiveConfig, createBridgeLiveConfig } from "./bridgeLiveConfig";
|
|
11
11
|
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
12
12
|
import { createBridgeNet } from "./bridgeNet";
|
|
13
13
|
import { createBridgeProbes } from "./bridgeProbe";
|
|
@@ -18,17 +18,18 @@ export interface ContextHooks {
|
|
|
18
18
|
onLog: BridgeLeveledLogger;
|
|
19
19
|
onEmit: (event: string, payload?: BridgeValues) => void;
|
|
20
20
|
onSetup: (runtime: BridgeSetupRuntime | null) => void;
|
|
21
|
+
onActivity: (entry: BridgeActivity) => void;
|
|
22
|
+
onRestart: () => Promise<void>;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export const createContext = (
|
|
24
26
|
config: BridgeConfig,
|
|
25
27
|
supervisor: BridgeSupervisor,
|
|
26
28
|
hooks: ContextHooks,
|
|
29
|
+
live: BridgeLiveConfig = createBridgeLiveConfig(configPath(), config),
|
|
27
30
|
): BridgeContext => {
|
|
28
31
|
const egress = createBridgeEgress(config);
|
|
29
32
|
|
|
30
|
-
const live = createBridgeLiveConfig(configPath(), config);
|
|
31
|
-
|
|
32
33
|
const files = createBridgeFiles({
|
|
33
34
|
root: config.cwd,
|
|
34
35
|
uid: config.gameUid,
|
|
@@ -37,6 +38,16 @@ export const createContext = (
|
|
|
37
38
|
onLog: hooks.onLog,
|
|
38
39
|
});
|
|
39
40
|
|
|
41
|
+
const portOf = (key: string) => {
|
|
42
|
+
const value = config.ports[key];
|
|
43
|
+
|
|
44
|
+
if (value === undefined) {
|
|
45
|
+
throw new Error(`this game declares no port named "${key}"`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return value;
|
|
49
|
+
};
|
|
50
|
+
|
|
40
51
|
return {
|
|
41
52
|
server: {
|
|
42
53
|
code: config.code,
|
|
@@ -49,24 +60,36 @@ export const createContext = (
|
|
|
49
60
|
get running() {
|
|
50
61
|
return supervisor.running;
|
|
51
62
|
},
|
|
63
|
+
get publicAddress() {
|
|
64
|
+
return live.publicAddress();
|
|
65
|
+
},
|
|
52
66
|
},
|
|
53
67
|
|
|
54
68
|
variable(key) {
|
|
55
69
|
return live.variable(key);
|
|
56
70
|
},
|
|
57
71
|
|
|
72
|
+
variables() {
|
|
73
|
+
return live.variables();
|
|
74
|
+
},
|
|
75
|
+
|
|
58
76
|
secret(key) {
|
|
59
77
|
return live.secret(key);
|
|
60
78
|
},
|
|
61
79
|
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
secrets() {
|
|
81
|
+
return live.secrets();
|
|
82
|
+
},
|
|
64
83
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
port(key) {
|
|
85
|
+
return portOf(key);
|
|
86
|
+
},
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
portBlock(key) {
|
|
89
|
+
return {
|
|
90
|
+
first: portOf(key),
|
|
91
|
+
count: config.portCounts?.[key] ?? 1,
|
|
92
|
+
};
|
|
70
93
|
},
|
|
71
94
|
|
|
72
95
|
files,
|
|
@@ -119,10 +142,20 @@ export const createContext = (
|
|
|
119
142
|
},
|
|
120
143
|
},
|
|
121
144
|
|
|
145
|
+
activity: {
|
|
146
|
+
report(entry) {
|
|
147
|
+
hooks.onActivity(entry);
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
|
|
122
151
|
emit(event, payload) {
|
|
123
152
|
hooks.onEmit(event, payload);
|
|
124
153
|
},
|
|
125
154
|
|
|
155
|
+
async restart() {
|
|
156
|
+
await hooks.onRestart();
|
|
157
|
+
},
|
|
158
|
+
|
|
126
159
|
log: Object.assign(
|
|
127
160
|
(message: string, fields?: BridgeValues) => {
|
|
128
161
|
hooks.onLog(message, {
|
|
@@ -2,7 +2,13 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { BridgeKind, BridgeSetupStepKind, PortJoin, PortProtocol } from "../core";
|
|
3
3
|
import { GuideOpenTab } from "../guides";
|
|
4
4
|
import type { ManifestPort, ManifestPortActivation } from "../manifest";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
type BridgeDriver,
|
|
7
|
+
BridgeLifecycleSubject,
|
|
8
|
+
type BridgePendingModule,
|
|
9
|
+
type BridgeSetupModule,
|
|
10
|
+
BridgeStopSignal,
|
|
11
|
+
} from "../modules";
|
|
6
12
|
import {
|
|
7
13
|
BridgeConfirm,
|
|
8
14
|
BridgeControl,
|
|
@@ -235,6 +241,7 @@ const port = (activeWhen: ManifestPortActivation[]): ManifestPort => ({
|
|
|
235
241
|
join: PortJoin.Combined,
|
|
236
242
|
activeWhen,
|
|
237
243
|
mirror: false,
|
|
244
|
+
count: 1,
|
|
238
245
|
});
|
|
239
246
|
|
|
240
247
|
const driverWithTabs = (tabs: number, sections: number): BridgeDriver => ({
|
|
@@ -268,6 +275,27 @@ const driverWithTabs = (tabs: number, sections: number): BridgeDriver => ({
|
|
|
268
275
|
});
|
|
269
276
|
|
|
270
277
|
describe("panel caps", () => {
|
|
278
|
+
test("rejects a collection row action named after a read action", () => {
|
|
279
|
+
const driver = driverWithTabs(1, 1);
|
|
280
|
+
|
|
281
|
+
driver.modules = {
|
|
282
|
+
...driver.modules,
|
|
283
|
+
worlds: {
|
|
284
|
+
kind: BridgeKind.Collection,
|
|
285
|
+
list: async () => [],
|
|
286
|
+
actions: {
|
|
287
|
+
preview: async () => {
|
|
288
|
+
return;
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
expect(() => validateDriver(driver)).toThrow(
|
|
295
|
+
'module "worlds" declares action "preview", which is reserved for reads',
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
271
299
|
test("takes the tab and section counts the agent mirror accepts", () => {
|
|
272
300
|
expect(() => validateDriver(driverWithTabs(8, 8))).not.toThrow();
|
|
273
301
|
});
|
|
@@ -1781,3 +1809,127 @@ describe("section placement", () => {
|
|
|
1781
1809
|
);
|
|
1782
1810
|
});
|
|
1783
1811
|
});
|
|
1812
|
+
|
|
1813
|
+
describe("the lifecycle module", () => {
|
|
1814
|
+
const lifecycleDriver = (lifecycle: Record<string, unknown>): BridgeDriver => ({
|
|
1815
|
+
install: {
|
|
1816
|
+
kind: BridgeKind.Install,
|
|
1817
|
+
run: async () => {
|
|
1818
|
+
return;
|
|
1819
|
+
},
|
|
1820
|
+
},
|
|
1821
|
+
lifecycle: {
|
|
1822
|
+
kind: BridgeKind.Lifecycle,
|
|
1823
|
+
command: async () => [
|
|
1824
|
+
"run",
|
|
1825
|
+
],
|
|
1826
|
+
...lifecycle,
|
|
1827
|
+
},
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
test("accepts an environment resolver", () => {
|
|
1831
|
+
expect(() =>
|
|
1832
|
+
validateDriver(
|
|
1833
|
+
lifecycleDriver({
|
|
1834
|
+
environment: async () => ({
|
|
1835
|
+
BOT_TOKEN: "token",
|
|
1836
|
+
}),
|
|
1837
|
+
}),
|
|
1838
|
+
),
|
|
1839
|
+
).not.toThrow();
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
test("accepts a lifecycle that declares no environment", () => {
|
|
1843
|
+
expect(() => validateDriver(lifecycleDriver({}))).not.toThrow();
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
test("accepts each stop signal the bridge sends", () => {
|
|
1847
|
+
for (const stopSignal of Object.values(BridgeStopSignal)) {
|
|
1848
|
+
expect(() =>
|
|
1849
|
+
validateDriver(
|
|
1850
|
+
lifecycleDriver({
|
|
1851
|
+
stopSignal,
|
|
1852
|
+
}),
|
|
1853
|
+
),
|
|
1854
|
+
).not.toThrow();
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
test("refuses a stop signal the bridge does not send", () => {
|
|
1859
|
+
expect(() =>
|
|
1860
|
+
validateDriver(
|
|
1861
|
+
lifecycleDriver({
|
|
1862
|
+
stopSignal: "SIGHUP",
|
|
1863
|
+
}),
|
|
1864
|
+
),
|
|
1865
|
+
).toThrow('the lifecycle module declares stop signal "SIGHUP" — the bridge sends only SIGTERM or SIGINT');
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
test("accepts a ready timeout from one second to an hour", () => {
|
|
1869
|
+
for (const readyTimeoutSeconds of [
|
|
1870
|
+
1,
|
|
1871
|
+
900,
|
|
1872
|
+
3600,
|
|
1873
|
+
]) {
|
|
1874
|
+
expect(() =>
|
|
1875
|
+
validateDriver(
|
|
1876
|
+
lifecycleDriver({
|
|
1877
|
+
readyTimeoutSeconds,
|
|
1878
|
+
}),
|
|
1879
|
+
),
|
|
1880
|
+
).not.toThrow();
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
|
|
1884
|
+
test("refuses a ready timeout outside one second to an hour", () => {
|
|
1885
|
+
for (const readyTimeoutSeconds of [
|
|
1886
|
+
0,
|
|
1887
|
+
3601,
|
|
1888
|
+
1.5,
|
|
1889
|
+
]) {
|
|
1890
|
+
expect(() =>
|
|
1891
|
+
validateDriver(
|
|
1892
|
+
lifecycleDriver({
|
|
1893
|
+
readyTimeoutSeconds,
|
|
1894
|
+
}),
|
|
1895
|
+
),
|
|
1896
|
+
).toThrow(
|
|
1897
|
+
`the lifecycle module declares a ready timeout of ${readyTimeoutSeconds} seconds — it must be a whole number from 1 to 3600`,
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
test("accepts each lifecycle subject the bridge names", () => {
|
|
1903
|
+
for (const subject of Object.values(BridgeLifecycleSubject)) {
|
|
1904
|
+
expect(() =>
|
|
1905
|
+
validateDriver(
|
|
1906
|
+
lifecycleDriver({
|
|
1907
|
+
subject,
|
|
1908
|
+
}),
|
|
1909
|
+
),
|
|
1910
|
+
).not.toThrow();
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
test("refuses a lifecycle subject the bridge does not name", () => {
|
|
1915
|
+
expect(() =>
|
|
1916
|
+
validateDriver(
|
|
1917
|
+
lifecycleDriver({
|
|
1918
|
+
subject: "server",
|
|
1919
|
+
}),
|
|
1920
|
+
),
|
|
1921
|
+
).toThrow('the lifecycle module declares subject "server" — the bridge names only game or bot');
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
test("refuses an environment that is not a function", () => {
|
|
1925
|
+
expect(() =>
|
|
1926
|
+
validateDriver(
|
|
1927
|
+
lifecycleDriver({
|
|
1928
|
+
environment: {
|
|
1929
|
+
BOT_TOKEN: "token",
|
|
1930
|
+
},
|
|
1931
|
+
}),
|
|
1932
|
+
),
|
|
1933
|
+
).toThrow("the lifecycle module declares an environment that is not a function");
|
|
1934
|
+
});
|
|
1935
|
+
});
|
|
@@ -3,11 +3,19 @@ import {
|
|
|
3
3
|
BRIDGE_MODULE_ID_PATTERN,
|
|
4
4
|
BridgeKind,
|
|
5
5
|
BridgeSetupStepKind,
|
|
6
|
+
isReadAction,
|
|
6
7
|
type LocalizedText,
|
|
7
8
|
} from "../core";
|
|
8
9
|
import { GuideOpenTab } from "../guides";
|
|
9
10
|
import type { ManifestPort } from "../manifest";
|
|
10
|
-
import
|
|
11
|
+
import {
|
|
12
|
+
type BridgeDriver,
|
|
13
|
+
BridgeLifecycleSubject,
|
|
14
|
+
type BridgePanelModule,
|
|
15
|
+
BridgeStopSignal,
|
|
16
|
+
isBridgeLifecycleSubject,
|
|
17
|
+
isBridgeStopSignal,
|
|
18
|
+
} from "../modules";
|
|
11
19
|
import {
|
|
12
20
|
BridgeControl,
|
|
13
21
|
type BridgeField,
|
|
@@ -50,6 +58,10 @@ import { actionsOf } from "./bridgeActions";
|
|
|
50
58
|
|
|
51
59
|
const UPLOAD_MODES = new Set<string>(Object.values(BridgeUploadMode));
|
|
52
60
|
|
|
61
|
+
const READY_TIMEOUT_MIN_SECONDS = 1;
|
|
62
|
+
|
|
63
|
+
const READY_TIMEOUT_MAX_SECONDS = 3600;
|
|
64
|
+
|
|
53
65
|
const RESERVED_MODULE_IDS = new Set([
|
|
54
66
|
"announce",
|
|
55
67
|
"backup",
|
|
@@ -591,6 +603,47 @@ const validateSection = (modules: Record<string, BridgePanelModule>, section: Br
|
|
|
591
603
|
}
|
|
592
604
|
};
|
|
593
605
|
|
|
606
|
+
const validateLifecycle = (driver: BridgeDriver) => {
|
|
607
|
+
const environment: unknown = driver.lifecycle.environment;
|
|
608
|
+
|
|
609
|
+
if (environment !== undefined && typeof environment !== "function") {
|
|
610
|
+
fail(
|
|
611
|
+
"the lifecycle module declares an environment that is not a function — it must resolve the variables the process starts with",
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const stopSignal: unknown = driver.lifecycle.stopSignal;
|
|
616
|
+
|
|
617
|
+
if (stopSignal !== undefined && !isBridgeStopSignal(stopSignal)) {
|
|
618
|
+
fail(
|
|
619
|
+
`the lifecycle module declares stop signal "${String(stopSignal)}" — the bridge sends only ${Object.values(BridgeStopSignal).join(" or ")}`,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const readyTimeoutSeconds: unknown = driver.lifecycle.readyTimeoutSeconds;
|
|
624
|
+
|
|
625
|
+
if (
|
|
626
|
+
readyTimeoutSeconds !== undefined
|
|
627
|
+
&& !(
|
|
628
|
+
Number.isInteger(readyTimeoutSeconds)
|
|
629
|
+
&& (readyTimeoutSeconds as number) >= READY_TIMEOUT_MIN_SECONDS
|
|
630
|
+
&& (readyTimeoutSeconds as number) <= READY_TIMEOUT_MAX_SECONDS
|
|
631
|
+
)
|
|
632
|
+
) {
|
|
633
|
+
fail(
|
|
634
|
+
`the lifecycle module declares a ready timeout of ${String(readyTimeoutSeconds)} seconds — it must be a whole number from ${READY_TIMEOUT_MIN_SECONDS} to ${READY_TIMEOUT_MAX_SECONDS}`,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const subject: unknown = driver.lifecycle.subject;
|
|
639
|
+
|
|
640
|
+
if (subject !== undefined && !isBridgeLifecycleSubject(subject)) {
|
|
641
|
+
fail(
|
|
642
|
+
`the lifecycle module declares subject "${String(subject)}" — the bridge names only ${Object.values(BridgeLifecycleSubject).join(" or ")}`,
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
594
647
|
const validatePending = (driver: BridgeDriver) => {
|
|
595
648
|
const pending = driver.pending;
|
|
596
649
|
|
|
@@ -732,6 +785,7 @@ export const validateDriver = (driver: BridgeDriver) => {
|
|
|
732
785
|
|
|
733
786
|
countIn(emitted.size, BRIDGE_EVENTS_LIMIT, "event names", "the driver");
|
|
734
787
|
|
|
788
|
+
validateLifecycle(driver);
|
|
735
789
|
validatePending(driver);
|
|
736
790
|
validateSetup(driver);
|
|
737
791
|
|
|
@@ -748,15 +802,17 @@ export const validateDriver = (driver: BridgeDriver) => {
|
|
|
748
802
|
countIn((module.dependsOn ?? []).length, BRIDGE_DEPENDS_ON_LIMIT, "dependencies", `module "${id}"`);
|
|
749
803
|
}
|
|
750
804
|
|
|
751
|
-
if (
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (module.kind === BridgeKind.Collection || module.kind === BridgeKind.Detail) {
|
|
805
|
+
if (
|
|
806
|
+
module.kind === BridgeKind.Actions
|
|
807
|
+
|| module.kind === BridgeKind.Collection
|
|
808
|
+
|| module.kind === BridgeKind.Detail
|
|
809
|
+
) {
|
|
758
810
|
for (const action of Object.keys(module.actions ?? {})) {
|
|
759
811
|
validateModuleId(action, `module "${id}" action`);
|
|
812
|
+
|
|
813
|
+
if (isReadAction(action)) {
|
|
814
|
+
fail(`module "${id}" declares action "${action}", which is reserved for reads — pick another id`);
|
|
815
|
+
}
|
|
760
816
|
}
|
|
761
817
|
}
|
|
762
818
|
}
|
|
@@ -564,6 +564,68 @@ describe("extract", () => {
|
|
|
564
564
|
expect(await read("config/beta.json")).toBe("beta");
|
|
565
565
|
});
|
|
566
566
|
|
|
567
|
+
test("keeps dotfiles in a tree when asked and never writes .git or .serverk", async () => {
|
|
568
|
+
const path = await archive([
|
|
569
|
+
{
|
|
570
|
+
name: "bot/.env.example",
|
|
571
|
+
content: "DISCORD_TOKEN=",
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "bot/src/.config/settings.json",
|
|
575
|
+
content: "{}",
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "bot/index.js",
|
|
579
|
+
content: "run()",
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: "bot/.git/config",
|
|
583
|
+
content: "[core]",
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: "bot/.serverk/deploy-requested",
|
|
587
|
+
content: "{}",
|
|
588
|
+
},
|
|
589
|
+
]);
|
|
590
|
+
|
|
591
|
+
const written = await files.extract(path, "", {
|
|
592
|
+
tree: true,
|
|
593
|
+
dotfiles: true,
|
|
594
|
+
select: "bot/",
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
expect(written).toEqual([
|
|
598
|
+
".env.example",
|
|
599
|
+
"src/.config/settings.json",
|
|
600
|
+
"index.js",
|
|
601
|
+
]);
|
|
602
|
+
|
|
603
|
+
expect(await read(".env.example")).toBe("DISCORD_TOKEN=");
|
|
604
|
+
expect(await files.exists(".git/config")).toBe(false);
|
|
605
|
+
expect(await files.exists(".serverk/deploy-requested")).toBe(false);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("still skips dotfiles in a tree unless asked", async () => {
|
|
609
|
+
const path = await archive([
|
|
610
|
+
{
|
|
611
|
+
name: ".env.example",
|
|
612
|
+
content: "DISCORD_TOKEN=",
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
name: "index.js",
|
|
616
|
+
content: "run()",
|
|
617
|
+
},
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
const written = await files.extract(path, "", {
|
|
621
|
+
tree: true,
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
expect(written).toEqual([
|
|
625
|
+
"index.js",
|
|
626
|
+
]);
|
|
627
|
+
});
|
|
628
|
+
|
|
567
629
|
test("takes a single named entry when the select carries no trailing slash", async () => {
|
|
568
630
|
const path = await archive([
|
|
569
631
|
{
|
|
@@ -152,6 +152,15 @@ const compileSelect = (select: string): ArchiveSelect => {
|
|
|
152
152
|
return select.endsWith("/") ? prefixSelect(select) : entrySelect(select);
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
+
const ALWAYS_SKIPPED_SEGMENTS: ReadonlySet<string> = new Set([
|
|
156
|
+
".git",
|
|
157
|
+
".serverk",
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
const hiddenEntry = (segments: string[], dotfiles: boolean) => {
|
|
161
|
+
return segments.some((segment) => ALWAYS_SKIPPED_SEGMENTS.has(segment) || (!dotfiles && segment.startsWith(".")));
|
|
162
|
+
};
|
|
163
|
+
|
|
155
164
|
const compileSelects = (select: string | undefined, selects: string[] | undefined) => {
|
|
156
165
|
const compiled: ArchiveSelect[] = [];
|
|
157
166
|
|
|
@@ -603,6 +612,7 @@ export const createBridgeFiles = (options: BridgeFilesOptions): BridgeFiles => {
|
|
|
603
612
|
}
|
|
604
613
|
|
|
605
614
|
const tree = extractOptions.tree === true;
|
|
615
|
+
const dotfiles = extractOptions.dotfiles === true;
|
|
606
616
|
const allowed = extractOptions.extensions?.map((extension) => extension.toLowerCase());
|
|
607
617
|
const selects = compileSelects(extractOptions.select, extractOptions.selects);
|
|
608
618
|
const excluded = compileGlobs(extractOptions.exclude ?? []);
|
|
@@ -638,7 +648,7 @@ export const createBridgeFiles = (options: BridgeFilesOptions): BridgeFiles => {
|
|
|
638
648
|
selected.split("/").at(-1) ?? "",
|
|
639
649
|
];
|
|
640
650
|
const name = segments.at(-1) ?? "";
|
|
641
|
-
if (!name || segments
|
|
651
|
+
if (!name || hiddenEntry(segments, dotfiles)) {
|
|
642
652
|
continue;
|
|
643
653
|
}
|
|
644
654
|
if (allowed !== undefined && !allowed.includes(name.split(".").at(-1)?.toLowerCase() ?? "")) {
|
|
@@ -677,7 +687,7 @@ export const createBridgeFiles = (options: BridgeFilesOptions): BridgeFiles => {
|
|
|
677
687
|
const name = segments.at(-1) ?? "";
|
|
678
688
|
const extension = name.split(".").at(-1)?.toLowerCase() ?? "";
|
|
679
689
|
|
|
680
|
-
if (name.length === 0 || segments
|
|
690
|
+
if (name.length === 0 || hiddenEntry(segments, dotfiles)) {
|
|
681
691
|
continue;
|
|
682
692
|
}
|
|
683
693
|
|
|
@@ -14,9 +14,13 @@ export const BRIDGE_CONFIG_REFRESH_MS = 2000;
|
|
|
14
14
|
*/
|
|
15
15
|
export interface BridgeLiveConfig {
|
|
16
16
|
variable: (key: string) => string | null;
|
|
17
|
+
variables: () => Readonly<Record<string, string>>;
|
|
17
18
|
secret: (key: string) => string | null;
|
|
19
|
+
secrets: () => Readonly<Record<string, string>>;
|
|
18
20
|
memoryMb: () => number;
|
|
19
21
|
cpuPercent: () => number;
|
|
22
|
+
publicAddress: () => string | null;
|
|
23
|
+
reload: () => void;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export const createBridgeLiveConfig = (
|
|
@@ -50,14 +54,28 @@ export const createBridgeLiveConfig = (
|
|
|
50
54
|
variable(key) {
|
|
51
55
|
return refresh().variables[key] ?? null;
|
|
52
56
|
},
|
|
57
|
+
variables() {
|
|
58
|
+
return refresh().variables;
|
|
59
|
+
},
|
|
53
60
|
secret(key) {
|
|
54
61
|
return refresh().secrets[key] ?? null;
|
|
55
62
|
},
|
|
63
|
+
secrets() {
|
|
64
|
+
return refresh().secrets;
|
|
65
|
+
},
|
|
56
66
|
memoryMb() {
|
|
57
67
|
return refresh().memoryMb;
|
|
58
68
|
},
|
|
59
69
|
cpuPercent() {
|
|
60
70
|
return refresh().cpuPercent;
|
|
61
71
|
},
|
|
72
|
+
publicAddress() {
|
|
73
|
+
return refresh().publicAddress ?? null;
|
|
74
|
+
},
|
|
75
|
+
reload() {
|
|
76
|
+
checkedAt = null;
|
|
77
|
+
|
|
78
|
+
refresh();
|
|
79
|
+
},
|
|
62
80
|
};
|
|
63
81
|
};
|
package/src/runtime/bridgeLog.ts
CHANGED
|
@@ -36,3 +36,13 @@ export const log: BridgeLeveledLogger = Object.assign(
|
|
|
36
36
|
export const errorText = (error: unknown) => {
|
|
37
37
|
return error instanceof Error ? error.message : String(error);
|
|
38
38
|
};
|
|
39
|
+
|
|
40
|
+
export const errorDetail = (error: unknown) => {
|
|
41
|
+
const text = errorText(error);
|
|
42
|
+
|
|
43
|
+
if (!(error instanceof Error) || error.cause === undefined) {
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return `${text} — ${errorText(error.cause)}`;
|
|
48
|
+
};
|