@serverkgg/bridge 0.1.7 → 0.1.8
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 +1 -0
- package/src/cli/validate.test.ts +429 -3
- package/src/cli/validate.ts +225 -1
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +79 -0
- package/src/manifest/manifest.ts +5 -0
- package/src/manifest/steam.ts +14 -0
- package/src/steam/steamcmd.test.ts +367 -1
- package/src/steam/steamcmd.ts +154 -18
package/src/steam/steamcmd.ts
CHANGED
|
@@ -16,6 +16,8 @@ const STEAM_CLIENT_DIRECTORY = `${STEAM_DIRECTORY}/sdk64`;
|
|
|
16
16
|
|
|
17
17
|
const SENTRY_FILE = ".sentry.b64";
|
|
18
18
|
|
|
19
|
+
const APP_INFO_FILE = "appinfo.vdf";
|
|
20
|
+
|
|
19
21
|
const UPDATE_TIMEOUT_MS = 3_600_000;
|
|
20
22
|
|
|
21
23
|
const BOOTSTRAP_TIMEOUT_MS = 300_000;
|
|
@@ -26,8 +28,43 @@ const ATTEMPTS = 3;
|
|
|
26
28
|
|
|
27
29
|
const RETRY_DELAY_MS = 5000;
|
|
28
30
|
|
|
31
|
+
const STATE_CODE = /\b0x[0-9a-f]+\b/gi;
|
|
32
|
+
|
|
33
|
+
const DISK_FULL_STATES = [
|
|
34
|
+
"0x202",
|
|
35
|
+
"0x212",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const TRANSIENT_STATES = [
|
|
39
|
+
"0x402",
|
|
40
|
+
"0x602",
|
|
41
|
+
"0x606",
|
|
42
|
+
"0x626",
|
|
43
|
+
"0x6a6",
|
|
44
|
+
];
|
|
45
|
+
|
|
29
46
|
const NO_SUBSCRIPTION = "No subscription";
|
|
30
47
|
|
|
48
|
+
const BRANCH_PASSWORD = "Password check for AppId";
|
|
49
|
+
|
|
50
|
+
const INVALID_PLATFORM = /invalid platform/i;
|
|
51
|
+
|
|
52
|
+
const MISSING_CONFIGURATION = /missing configuration/i;
|
|
53
|
+
|
|
54
|
+
export enum SteamcmdFailure {
|
|
55
|
+
DiskFull = "disk-full",
|
|
56
|
+
InvalidPlatform = "invalid-platform",
|
|
57
|
+
NoSubscription = "no-subscription",
|
|
58
|
+
BranchPassword = "branch-password",
|
|
59
|
+
Transient = "transient",
|
|
60
|
+
Unknown = "unknown",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const RETRYABLE_FAILURES = new Set([
|
|
64
|
+
SteamcmdFailure.Transient,
|
|
65
|
+
SteamcmdFailure.Unknown,
|
|
66
|
+
]);
|
|
67
|
+
|
|
31
68
|
export interface SteamcmdOptions {
|
|
32
69
|
appId: string;
|
|
33
70
|
label: string;
|
|
@@ -80,6 +117,8 @@ export const steamcmdArgv = (options: SteamcmdOptions, root: string, args: strin
|
|
|
80
117
|
|
|
81
118
|
export const steamcmdUpdateArgs = (appId: string, update: SteamcmdUpdateOptions) => {
|
|
82
119
|
const args = [
|
|
120
|
+
"+app_info_update",
|
|
121
|
+
"1",
|
|
83
122
|
"+app_update",
|
|
84
123
|
appId,
|
|
85
124
|
];
|
|
@@ -103,18 +142,67 @@ export const steamcmdUpdateArgs = (appId: string, update: SteamcmdUpdateOptions)
|
|
|
103
142
|
return args;
|
|
104
143
|
};
|
|
105
144
|
|
|
106
|
-
export const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
145
|
+
export const steamcmdFailureOf = (result: BridgeExecResult): SteamcmdFailure => {
|
|
146
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
147
|
+
const states = new Set(Array.from(output.matchAll(STATE_CODE), (match) => match[0].toLowerCase()));
|
|
148
|
+
|
|
149
|
+
if (DISK_FULL_STATES.some((state) => states.has(state))) {
|
|
150
|
+
return SteamcmdFailure.DiskFull;
|
|
112
151
|
}
|
|
113
152
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
153
|
+
if (INVALID_PLATFORM.test(output)) {
|
|
154
|
+
return SteamcmdFailure.InvalidPlatform;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (output.includes(NO_SUBSCRIPTION)) {
|
|
158
|
+
return SteamcmdFailure.NoSubscription;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (output.includes(BRANCH_PASSWORD)) {
|
|
162
|
+
return SteamcmdFailure.BranchPassword;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (TRANSIENT_STATES.some((state) => states.has(state)) || MISSING_CONFIGURATION.test(output)) {
|
|
166
|
+
return SteamcmdFailure.Transient;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return SteamcmdFailure.Unknown;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const steamcmdRetryable = (failure: SteamcmdFailure) => {
|
|
173
|
+
return RETRYABLE_FAILURES.has(failure);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const steamcmdFailureText = (result: BridgeExecResult, label: string): LocalizedText => {
|
|
177
|
+
switch (steamcmdFailureOf(result)) {
|
|
178
|
+
case SteamcmdFailure.DiskFull: {
|
|
179
|
+
return {
|
|
180
|
+
ar: `امتلأت مساحة سيرفرك، فتعذّر تنزيل ${label} من Steam. احذف الملفات التي لا تحتاجها من صفحة الملفات أو انتقل إلى باقة أكبر، ثم جرب مرة ثانية.`,
|
|
181
|
+
en: `Your server is out of space, so we couldn't download ${label} from Steam. Delete files you don't need on the Files page or move to a larger plan, then try again.`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
case SteamcmdFailure.NoSubscription: {
|
|
186
|
+
return {
|
|
187
|
+
ar: `رفض Steam تنزيل ${label} لأن حساب التنزيل لا يملكه. تواصل مع الدعم.`,
|
|
188
|
+
en: `Steam refused to download ${label} because the download account doesn't own it. Contact support.`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case SteamcmdFailure.BranchPassword: {
|
|
193
|
+
return {
|
|
194
|
+
ar: `كلمة مرور الفرع على Steam غير صحيحة، فتعذّر تنزيل ${label}. صحّحها من الإعدادات.`,
|
|
195
|
+
en: `The Steam branch password is wrong, so we couldn't download ${label}. Fix it in the settings.`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
default: {
|
|
200
|
+
return {
|
|
201
|
+
ar: `تعذّر تنزيل ${label} من Steam، جرب مرة ثانية.`,
|
|
202
|
+
en: `We couldn't download ${label} from Steam. Try again.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
118
206
|
};
|
|
119
207
|
|
|
120
208
|
const installRoot = async (context: BridgeContext) => {
|
|
@@ -197,15 +285,20 @@ export const createSteamcmd = (context: BridgeContext, options: SteamcmdOptions)
|
|
|
197
285
|
|
|
198
286
|
failure = result;
|
|
199
287
|
|
|
200
|
-
|
|
201
|
-
context.log("steamcmd did not finish, trying again", {
|
|
202
|
-
attempt,
|
|
203
|
-
of: attempts,
|
|
204
|
-
code: result.code,
|
|
205
|
-
});
|
|
288
|
+
const kind = steamcmdFailureOf(result);
|
|
206
289
|
|
|
207
|
-
|
|
290
|
+
if (attempt === attempts || !steamcmdRetryable(kind)) {
|
|
291
|
+
break;
|
|
208
292
|
}
|
|
293
|
+
|
|
294
|
+
context.log("steamcmd did not finish, trying again", {
|
|
295
|
+
attempt,
|
|
296
|
+
of: attempts,
|
|
297
|
+
code: result.code,
|
|
298
|
+
failure: kind,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await Bun.sleep(RETRY_DELAY_MS);
|
|
209
302
|
}
|
|
210
303
|
|
|
211
304
|
if (!failure) {
|
|
@@ -213,10 +306,45 @@ export const createSteamcmd = (context: BridgeContext, options: SteamcmdOptions)
|
|
|
213
306
|
}
|
|
214
307
|
|
|
215
308
|
throw new BridgeUserError(steamcmdFailureText(failure, options.label), {
|
|
216
|
-
cause: new Error(
|
|
309
|
+
cause: new Error(
|
|
310
|
+
`steamcmd exited with code ${failure.code} (${steamcmdFailureOf(failure)}) — ${execDetail(failure)}`,
|
|
311
|
+
),
|
|
217
312
|
});
|
|
218
313
|
};
|
|
219
314
|
|
|
315
|
+
const clearAppInfo = async () => {
|
|
316
|
+
const roots: string[] = [];
|
|
317
|
+
|
|
318
|
+
for (const path of [
|
|
319
|
+
directory,
|
|
320
|
+
STEAM_DIRECTORY,
|
|
321
|
+
]) {
|
|
322
|
+
if (await context.files.exists(path)) {
|
|
323
|
+
roots.push(path);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (roots.length === 0) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const result = await context.exec([
|
|
332
|
+
"find",
|
|
333
|
+
...roots,
|
|
334
|
+
"-type",
|
|
335
|
+
"f",
|
|
336
|
+
"-name",
|
|
337
|
+
APP_INFO_FILE,
|
|
338
|
+
"-delete",
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
if (result.code !== 0) {
|
|
342
|
+
context.log.warn("the cached steam app info could not be cleared, updating anyway", {
|
|
343
|
+
code: result.code,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
220
348
|
return {
|
|
221
349
|
directory,
|
|
222
350
|
script,
|
|
@@ -271,7 +399,15 @@ export const createSteamcmd = (context: BridgeContext, options: SteamcmdOptions)
|
|
|
271
399
|
run,
|
|
272
400
|
|
|
273
401
|
async update(update) {
|
|
402
|
+
await clearAppInfo();
|
|
274
403
|
await run(steamcmdUpdateArgs(options.appId, update));
|
|
404
|
+
|
|
405
|
+
const build = await installedBuildId(context, options.appId);
|
|
406
|
+
|
|
407
|
+
context.log("steamcmd finished the update", {
|
|
408
|
+
app: options.appId,
|
|
409
|
+
build,
|
|
410
|
+
});
|
|
275
411
|
},
|
|
276
412
|
|
|
277
413
|
async buildId() {
|