@serverkgg/bridge 0.1.1 → 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 +41 -11
- package/src/bot/botPanel.ts +13 -5
- 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 +123 -29
- package/src/cli/validate.ts +37 -1
- 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/context/bridgeContext.ts +6 -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 +6 -6
- package/src/core/bridgeUserError.test.ts +13 -3
- package/src/core/bridgeUserError.ts +6 -2
- package/src/core/bridgeValue.test.ts +5 -5
- 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/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/modules/bridge.ts +8 -1
- package/src/modules/bridgeModule.ts +13 -1
- package/src/net/loopback.test.ts +10 -10
- package/src/panel/bridgeDetail.ts +2 -1
- 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/bridgeActivity.ts +54 -0
- package/src/protocol/bridgeEvents.ts +30 -0
- package/src/protocol/bridgeFrame.ts +10 -1
- package/src/protocol/index.ts +1 -0
- 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/bridgeActions.ts +19 -2
- package/src/runtime/bridgeActivity.test.ts +318 -0
- package/src/runtime/bridgeActivity.ts +268 -0
- 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 +11 -5
- package/src/runtime/bridgeContext.ts +8 -1
- package/src/runtime/bridgeDriver.test.ts +114 -93
- package/src/runtime/bridgeDriver.ts +10 -7
- 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/bridgeLog.ts +10 -0
- package/src/runtime/bridgePending.test.ts +6 -6
- package/src/runtime/bridgeRedaction.test.ts +16 -16
- package/src/runtime/bridgeRefusalError.ts +13 -0
- package/src/runtime/bridgeRegistry.test.ts +1077 -34
- package/src/runtime/bridgeRegistry.ts +464 -65
- package/src/runtime/bridgeScopedContext.ts +55 -0
- package/src/runtime/bridgeServer.test.ts +424 -24
- package/src/runtime/bridgeServer.ts +125 -35
- package/src/runtime/bridgeSetup.test.ts +27 -27
- package/src/runtime/bridgeSetup.ts +1 -1
- package/src/runtime/bridgeSetupHandlers.test.ts +80 -17
- package/src/runtime/bridgeSetupHandlers.ts +45 -10
- package/src/runtime/bridgeSignal.test.ts +7 -7
- package/src/runtime/bridgeSupervisor.test.ts +42 -42
- package/src/runtime/serve.ts +45 -3
- package/src/steam/avatars.test.ts +13 -13
- package/src/steam/steamApp.test.ts +8 -8
- package/src/steam/steamcmd.test.ts +44 -13
- package/src/steam/steamcmd.ts +27 -4
- package/src/steam/steamcmdNarrator.test.ts +4 -4
- package/src/steam/workshop.test.ts +5 -5
- 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/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 +95 -0
- package/src/wire/activity.ts +41 -0
- package/src/wire/index.ts +1 -0
- package/src/wire/panel.test.ts +91 -25
- package/src/wire/panel.ts +11 -0
|
@@ -3,6 +3,7 @@ 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";
|
|
@@ -801,15 +802,17 @@ export const validateDriver = (driver: BridgeDriver) => {
|
|
|
801
802
|
countIn((module.dependsOn ?? []).length, BRIDGE_DEPENDS_ON_LIMIT, "dependencies", `module "${id}"`);
|
|
802
803
|
}
|
|
803
804
|
|
|
804
|
-
if (
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
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
|
+
) {
|
|
811
810
|
for (const action of Object.keys(module.actions ?? {})) {
|
|
812
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
|
+
}
|
|
813
816
|
}
|
|
814
817
|
}
|
|
815
818
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -162,19 +162,19 @@ const joined = {
|
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
describe("matching a game line against the driver patterns", () => {
|
|
165
|
-
|
|
165
|
+
it("emits on the plain line", async () => {
|
|
166
166
|
expect(await emittedFor("[World|default] Player 'Meslzy' joined world default")).toEqual([
|
|
167
167
|
joined,
|
|
168
168
|
]);
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
it("emits on the line hytale really prints, wrapped in ansi resets at both ends", async () => {
|
|
172
172
|
expect(await emittedFor(`${RESET}[World|default] Player 'Meslzy' joined world default${RESET}`)).toEqual([
|
|
173
173
|
joined,
|
|
174
174
|
]);
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
it("holds a matched diagnostic line as evidence and spends it on the crash record", async () => {
|
|
178
178
|
const { held, events: drained } = await crashAfter(
|
|
179
179
|
"Exception in mod journeymap from journeymap-5.9.jar",
|
|
180
180
|
crashRecord({
|
|
@@ -205,7 +205,7 @@ describe("matching a game line against the driver patterns", () => {
|
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
describe("the crash record the ring builds", () => {
|
|
208
|
-
|
|
208
|
+
it("holds evidence out of the ring and lets every other event drain", () => {
|
|
209
209
|
const ring = idleRing();
|
|
210
210
|
|
|
211
211
|
ring.emit(BridgeEventName.ModCrashed, {
|
|
@@ -221,7 +221,7 @@ describe("the crash record the ring builds", () => {
|
|
|
221
221
|
]);
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
it("prefers the oom kill over any evidence it holds", () => {
|
|
225
225
|
const ring = idleRing();
|
|
226
226
|
|
|
227
227
|
ring.emit(BridgeEventName.ModCrashed, {
|
|
@@ -246,7 +246,7 @@ describe("the crash record the ring builds", () => {
|
|
|
246
246
|
});
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
it("follows the evidence precedence when more than one name is held", () => {
|
|
250
250
|
const ring = idleRing();
|
|
251
251
|
|
|
252
252
|
ring.emit(BridgeEventName.ModCrashed, {
|
|
@@ -271,7 +271,7 @@ describe("the crash record the ring builds", () => {
|
|
|
271
271
|
});
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
it("lets the out of memory log pattern outrank the java version", () => {
|
|
275
275
|
const ring = idleRing();
|
|
276
276
|
|
|
277
277
|
ring.emit(BridgeEventName.WrongJavaVersion, {
|
|
@@ -290,7 +290,7 @@ describe("the crash record the ring builds", () => {
|
|
|
290
290
|
});
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
it("never lets the evidence overwrite the record keys", () => {
|
|
294
294
|
const ring = idleRing();
|
|
295
295
|
|
|
296
296
|
ring.emit(BridgeEventName.ModCrashed, {
|
|
@@ -310,7 +310,7 @@ describe("the crash record the ring builds", () => {
|
|
|
310
310
|
});
|
|
311
311
|
});
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
it("clears the evidence once it is spent", () => {
|
|
314
314
|
const ring = idleRing();
|
|
315
315
|
|
|
316
316
|
ring.emit(BridgeEventName.ModCrashed, {
|
|
@@ -332,7 +332,7 @@ describe("the crash record the ring builds", () => {
|
|
|
332
332
|
});
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
|
|
335
|
+
it("carries the last line only when nothing explains the crash", () => {
|
|
336
336
|
const ring = idleRing();
|
|
337
337
|
|
|
338
338
|
ring.emit(BridgeEventName.ModCrashed, {
|
|
@@ -360,7 +360,7 @@ describe("the crash record the ring builds", () => {
|
|
|
360
360
|
});
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
-
|
|
363
|
+
it("keeps listing the evidence names the driver declares", () => {
|
|
364
364
|
expect(idleRing().names()).toEqual([
|
|
365
365
|
BridgeEventName.PlayerJoined,
|
|
366
366
|
BridgeEventName.ModCrashed,
|
|
@@ -433,7 +433,7 @@ const crashedEvent = async (ring: BridgeEventRing) => {
|
|
|
433
433
|
};
|
|
434
434
|
|
|
435
435
|
describe("the evidence a run may spend", () => {
|
|
436
|
-
|
|
436
|
+
it("spends nothing printed before the run became stable on a running crash", async () => {
|
|
437
437
|
const wiring = await wired(EVIDENCE_BEFORE_READY, READY_LINE);
|
|
438
438
|
|
|
439
439
|
await wiring.supervisor.start();
|
|
@@ -452,7 +452,7 @@ describe("the evidence a run may spend", () => {
|
|
|
452
452
|
}
|
|
453
453
|
}, 20_000);
|
|
454
454
|
|
|
455
|
-
|
|
455
|
+
it("spends what the run printed after it became stable", async () => {
|
|
456
456
|
const wiring = await wired(EVIDENCE_AFTER_READY, READY_LINE);
|
|
457
457
|
|
|
458
458
|
await wiring.supervisor.start();
|
|
@@ -470,7 +470,7 @@ describe("the evidence a run may spend", () => {
|
|
|
470
470
|
}
|
|
471
471
|
}, 20_000);
|
|
472
472
|
|
|
473
|
-
|
|
473
|
+
it("drops what it holds when the stop was requested", async () => {
|
|
474
474
|
const wiring = await wired(EVIDENCE_THEN_IDLE);
|
|
475
475
|
|
|
476
476
|
await wiring.supervisor.start();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { crc32, deflateRawSync } from "node:zlib";
|
|
@@ -205,7 +205,7 @@ describe("extract", () => {
|
|
|
205
205
|
});
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
it("flattens paths, skips dotfiles and filters extensions by default", async () => {
|
|
209
209
|
const path = await archive([
|
|
210
210
|
{
|
|
211
211
|
name: "overrides/mods/alpha.jar",
|
|
@@ -234,7 +234,7 @@ describe("extract", () => {
|
|
|
234
234
|
expect(await read("mods/alpha.jar")).toBe("alpha");
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
it("preflights duplicate paths, parent collisions and expansion before writing", async () => {
|
|
238
238
|
for (const entries of [
|
|
239
239
|
[
|
|
240
240
|
{
|
|
@@ -285,7 +285,7 @@ describe("extract", () => {
|
|
|
285
285
|
expect(await Bun.file(`${root}/staging/a.txt`).exists()).toBe(false);
|
|
286
286
|
});
|
|
287
287
|
|
|
288
|
-
|
|
288
|
+
it("allows a bounded per-entry runtime limit without bypassing archive limits", async () => {
|
|
289
289
|
const path = await archive([
|
|
290
290
|
{
|
|
291
291
|
name: "runtime",
|
|
@@ -323,7 +323,7 @@ describe("extract", () => {
|
|
|
323
323
|
expect(await read("staging/runtime")).toBe("12345678");
|
|
324
324
|
});
|
|
325
325
|
|
|
326
|
-
|
|
326
|
+
it("refuses unsafe unselected entries instead of accepting a partly safe archive", async () => {
|
|
327
327
|
const path = await archive([
|
|
328
328
|
{
|
|
329
329
|
name: "safe/a.txt",
|
|
@@ -343,7 +343,7 @@ describe("extract", () => {
|
|
|
343
343
|
expect(await Bun.file(`${root}/staging/a.txt`).exists()).toBe(false);
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
-
|
|
346
|
+
it("flattens paths with no options at all", async () => {
|
|
347
347
|
const path = await archive([
|
|
348
348
|
{
|
|
349
349
|
name: "nested/deep/alpha.jar",
|
|
@@ -363,7 +363,7 @@ describe("extract", () => {
|
|
|
363
363
|
]);
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
-
|
|
366
|
+
it("keeps relative paths and creates intermediate directories in tree mode", async () => {
|
|
367
367
|
const path = await archive([
|
|
368
368
|
{
|
|
369
369
|
name: "mods/alpha.jar",
|
|
@@ -388,7 +388,7 @@ describe("extract", () => {
|
|
|
388
388
|
expect(await read("staging/config/deep/beta.json")).toBe("beta");
|
|
389
389
|
});
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
it("keeps the dotfile skip in tree mode", async () => {
|
|
392
392
|
const path = await archive([
|
|
393
393
|
{
|
|
394
394
|
name: "mods/alpha.jar",
|
|
@@ -413,7 +413,7 @@ describe("extract", () => {
|
|
|
413
413
|
]);
|
|
414
414
|
});
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
it("rejects absolute entries in tree mode", async () => {
|
|
417
417
|
const path = await archive([
|
|
418
418
|
{
|
|
419
419
|
name: "/etc/passwd",
|
|
@@ -428,7 +428,7 @@ describe("extract", () => {
|
|
|
428
428
|
).rejects.toThrow("absolute path");
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
-
|
|
431
|
+
it("rejects escaping entries in tree mode", async () => {
|
|
432
432
|
const path = await archive([
|
|
433
433
|
{
|
|
434
434
|
name: "overrides/../../escape.json",
|
|
@@ -443,7 +443,7 @@ describe("extract", () => {
|
|
|
443
443
|
).rejects.toThrow("is not allowed");
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
-
|
|
446
|
+
it("rejects symlink entries in tree mode", async () => {
|
|
447
447
|
const path = await archive([
|
|
448
448
|
{
|
|
449
449
|
name: "mods/link.jar",
|
|
@@ -459,7 +459,7 @@ describe("extract", () => {
|
|
|
459
459
|
).rejects.toThrow("symlink");
|
|
460
460
|
});
|
|
461
461
|
|
|
462
|
-
|
|
462
|
+
it("extracts only the selected prefix and strips it", async () => {
|
|
463
463
|
const path = await archive([
|
|
464
464
|
{
|
|
465
465
|
name: "overrides/mods/alpha.jar",
|
|
@@ -486,7 +486,7 @@ describe("extract", () => {
|
|
|
486
486
|
expect(await read("alpha.jar")).toBe("alpha");
|
|
487
487
|
});
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
it("composes select with tree", async () => {
|
|
490
490
|
const path = await archive([
|
|
491
491
|
{
|
|
492
492
|
name: "overrides/mods/alpha.jar",
|
|
@@ -515,7 +515,7 @@ describe("extract", () => {
|
|
|
515
515
|
expect(await read("config/deep/beta.json")).toBe("beta");
|
|
516
516
|
});
|
|
517
517
|
|
|
518
|
-
|
|
518
|
+
it("fails when nothing matches", async () => {
|
|
519
519
|
const path = await archive([
|
|
520
520
|
{
|
|
521
521
|
name: "overrides/mods/alpha.jar",
|
|
@@ -531,7 +531,7 @@ describe("extract", () => {
|
|
|
531
531
|
).rejects.toThrow("contained no matching files");
|
|
532
532
|
});
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
it("takes every selected prefix in one pass and strips the one that matched", async () => {
|
|
535
535
|
const path = await archive([
|
|
536
536
|
{
|
|
537
537
|
name: "overrides/mods/alpha.jar",
|
|
@@ -564,7 +564,7 @@ describe("extract", () => {
|
|
|
564
564
|
expect(await read("config/beta.json")).toBe("beta");
|
|
565
565
|
});
|
|
566
566
|
|
|
567
|
-
|
|
567
|
+
it("keeps dotfiles in a tree when asked and never writes .git or .serverk", async () => {
|
|
568
568
|
const path = await archive([
|
|
569
569
|
{
|
|
570
570
|
name: "bot/.env.example",
|
|
@@ -605,7 +605,7 @@ describe("extract", () => {
|
|
|
605
605
|
expect(await files.exists(".serverk/deploy-requested")).toBe(false);
|
|
606
606
|
});
|
|
607
607
|
|
|
608
|
-
|
|
608
|
+
it("still skips dotfiles in a tree unless asked", async () => {
|
|
609
609
|
const path = await archive([
|
|
610
610
|
{
|
|
611
611
|
name: ".env.example",
|
|
@@ -626,7 +626,7 @@ describe("extract", () => {
|
|
|
626
626
|
]);
|
|
627
627
|
});
|
|
628
628
|
|
|
629
|
-
|
|
629
|
+
it("takes a single named entry when the select carries no trailing slash", async () => {
|
|
630
630
|
const path = await archive([
|
|
631
631
|
{
|
|
632
632
|
name: "modrinth.index.json",
|
|
@@ -652,7 +652,7 @@ describe("extract", () => {
|
|
|
652
652
|
expect(await read("staging/modrinth.index.json")).toBe("{}");
|
|
653
653
|
});
|
|
654
654
|
|
|
655
|
-
|
|
655
|
+
it("drops excluded paths after the prefix is stripped", async () => {
|
|
656
656
|
const path = await archive([
|
|
657
657
|
{
|
|
658
658
|
name: "overrides/mods/alpha.jar",
|
|
@@ -686,7 +686,7 @@ describe("extract", () => {
|
|
|
686
686
|
expect(await Bun.file(`${root}/shaderpacks/heavy.zip`).exists()).toBe(false);
|
|
687
687
|
});
|
|
688
688
|
|
|
689
|
-
|
|
689
|
+
it("answers with nothing when a select matches nothing", async () => {
|
|
690
690
|
const path = await archive([
|
|
691
691
|
{
|
|
692
692
|
name: "overrides/mods/alpha.jar",
|
|
@@ -704,7 +704,7 @@ describe("extract", () => {
|
|
|
704
704
|
expect(written).toEqual([]);
|
|
705
705
|
});
|
|
706
706
|
|
|
707
|
-
|
|
707
|
+
it("inflates a deflated entry", async () => {
|
|
708
708
|
const path = await archive([
|
|
709
709
|
{
|
|
710
710
|
name: "mods/alpha.jar",
|
|
@@ -722,7 +722,7 @@ describe("extract", () => {
|
|
|
722
722
|
expect(await read("mods/alpha.jar")).toBe("alpha the deflated");
|
|
723
723
|
});
|
|
724
724
|
|
|
725
|
-
|
|
725
|
+
it("rejects stored data corruption before writing the destination", async () => {
|
|
726
726
|
const data = buildZip([
|
|
727
727
|
{
|
|
728
728
|
name: "alpha.jar",
|
|
@@ -735,7 +735,7 @@ describe("extract", () => {
|
|
|
735
735
|
expect(await Bun.file(`${root}/mods/alpha.jar`).exists()).toBe(false);
|
|
736
736
|
});
|
|
737
737
|
|
|
738
|
-
|
|
738
|
+
it("rejects deflated data with a valid size but incorrect checksum", async () => {
|
|
739
739
|
const path = await archive([
|
|
740
740
|
{
|
|
741
741
|
name: "alpha.jar",
|
|
@@ -747,7 +747,7 @@ describe("extract", () => {
|
|
|
747
747
|
expect(await Bun.file(`${root}/mods/alpha.jar`).exists()).toBe(false);
|
|
748
748
|
});
|
|
749
749
|
|
|
750
|
-
|
|
750
|
+
it("rejects local and central compression method disagreement", async () => {
|
|
751
751
|
const data = buildZip([
|
|
752
752
|
{
|
|
753
753
|
name: "alpha.jar",
|
|
@@ -759,7 +759,7 @@ describe("extract", () => {
|
|
|
759
759
|
await expect(files.extract("corrupt.zip", "mods")).rejects.toThrow("method");
|
|
760
760
|
});
|
|
761
761
|
|
|
762
|
-
|
|
762
|
+
it("rejects local names that differ from the central directory", async () => {
|
|
763
763
|
const data = buildZip([
|
|
764
764
|
{
|
|
765
765
|
name: "alpha.jar",
|
|
@@ -771,7 +771,7 @@ describe("extract", () => {
|
|
|
771
771
|
await expect(files.extract("corrupt.zip", "mods")).rejects.toThrow("local name");
|
|
772
772
|
});
|
|
773
773
|
|
|
774
|
-
|
|
774
|
+
it("rejects a local variable header that crosses into the central directory", async () => {
|
|
775
775
|
const data = buildZip([
|
|
776
776
|
{
|
|
777
777
|
name: "alpha.jar",
|
|
@@ -783,7 +783,7 @@ describe("extract", () => {
|
|
|
783
783
|
await expect(files.extract("corrupt.zip", "mods")).rejects.toThrow("bounds");
|
|
784
784
|
});
|
|
785
785
|
|
|
786
|
-
|
|
786
|
+
it("rejects compressed data ranges that cross the central directory", async () => {
|
|
787
787
|
const data = buildZip([
|
|
788
788
|
{
|
|
789
789
|
name: "alpha.jar",
|
|
@@ -797,7 +797,7 @@ describe("extract", () => {
|
|
|
797
797
|
await expect(files.extract("corrupt.zip", "mods")).rejects.toThrow("bounds");
|
|
798
798
|
});
|
|
799
799
|
|
|
800
|
-
|
|
800
|
+
it("accepts descriptor entries with zero local checksum and validates the central checksum", async () => {
|
|
801
801
|
const path = await archive(
|
|
802
802
|
[
|
|
803
803
|
{
|
|
@@ -814,7 +814,7 @@ describe("extract", () => {
|
|
|
814
814
|
expect(await read("mods/alpha.jar")).toBe("alpha");
|
|
815
815
|
});
|
|
816
816
|
|
|
817
|
-
|
|
817
|
+
it("rejects local checksum disagreement", async () => {
|
|
818
818
|
const data = buildZip([
|
|
819
819
|
{
|
|
820
820
|
name: "alpha.jar",
|
|
@@ -826,7 +826,7 @@ describe("extract", () => {
|
|
|
826
826
|
await expect(files.extract("corrupt.zip", "mods")).rejects.toThrow("local checksum");
|
|
827
827
|
});
|
|
828
828
|
|
|
829
|
-
|
|
829
|
+
it("rejects local compressed-size disagreement", async () => {
|
|
830
830
|
const data = buildZip([
|
|
831
831
|
{
|
|
832
832
|
name: "alpha.jar",
|
|
@@ -838,7 +838,7 @@ describe("extract", () => {
|
|
|
838
838
|
await expect(files.extract("corrupt.zip", "mods")).rejects.toThrow("local sizes");
|
|
839
839
|
});
|
|
840
840
|
|
|
841
|
-
|
|
841
|
+
it("reads a zip64 archive through its locator and extended field", async () => {
|
|
842
842
|
const path = await archive(
|
|
843
843
|
[
|
|
844
844
|
{
|
|
@@ -870,7 +870,7 @@ describe("extract", () => {
|
|
|
870
870
|
expect(await read("config/beta.json")).toBe("beta");
|
|
871
871
|
});
|
|
872
872
|
|
|
873
|
-
|
|
873
|
+
it("takes a suffix select from anywhere in the archive", async () => {
|
|
874
874
|
const path = await archive([
|
|
875
875
|
{
|
|
876
876
|
name: "BMC5 Server/mods/alpha.jar",
|
|
@@ -902,7 +902,7 @@ describe("extract", () => {
|
|
|
902
902
|
expect(await read("staging/beta.jar")).toBe("beta");
|
|
903
903
|
});
|
|
904
904
|
|
|
905
|
-
|
|
905
|
+
it("keeps the suffix path under the destination in tree mode", async () => {
|
|
906
906
|
const path = await archive([
|
|
907
907
|
{
|
|
908
908
|
name: "BMC5 Server/mods/alpha.jar",
|
|
@@ -922,7 +922,7 @@ describe("extract", () => {
|
|
|
922
922
|
]);
|
|
923
923
|
});
|
|
924
924
|
|
|
925
|
-
|
|
925
|
+
it("never inflates an entry it did not select", async () => {
|
|
926
926
|
const entries: ArchiveEntry[] = [
|
|
927
927
|
{
|
|
928
928
|
name: "BMC5 Server/mods/alpha.jar",
|
|
@@ -956,7 +956,7 @@ describe("extract", () => {
|
|
|
956
956
|
).rejects.toThrow();
|
|
957
957
|
});
|
|
958
958
|
|
|
959
|
-
|
|
959
|
+
it("answers with nothing when every match is excluded", async () => {
|
|
960
960
|
const path = await archive([
|
|
961
961
|
{
|
|
962
962
|
name: "overrides/shaderpacks/heavy.zip",
|
|
@@ -997,7 +997,7 @@ describe("list", () => {
|
|
|
997
997
|
recursive: true,
|
|
998
998
|
});
|
|
999
999
|
});
|
|
1000
|
-
|
|
1000
|
+
it("filters before the result cap and scopes metadata beyond a large runtime tree", async () => {
|
|
1001
1001
|
for (let start = 0; start < 4097; start += 64) {
|
|
1002
1002
|
await Promise.all(
|
|
1003
1003
|
Array.from(
|
|
@@ -1027,7 +1027,7 @@ describe("list", () => {
|
|
|
1027
1027
|
]);
|
|
1028
1028
|
await expect(files.list("**/*")).rejects.toThrow("result limit");
|
|
1029
1029
|
});
|
|
1030
|
-
|
|
1030
|
+
it("nonrecursive globs ignore unrelated deep trees and respect wildcard directory boundaries", async () => {
|
|
1031
1031
|
await files.write("server.jar", "jar");
|
|
1032
1032
|
await files.write("server.properties", "settings");
|
|
1033
1033
|
await files.write("mods/custom/plugin.jar", "plugin");
|
|
@@ -1048,7 +1048,7 @@ describe("list", () => {
|
|
|
1048
1048
|
"server.jar",
|
|
1049
1049
|
]);
|
|
1050
1050
|
});
|
|
1051
|
-
|
|
1051
|
+
it("prunes subtrees past the depth limit instead of failing the whole listing", async () => {
|
|
1052
1052
|
const nested = Array.from(
|
|
1053
1053
|
{
|
|
1054
1054
|
length: 14,
|
|
@@ -1078,7 +1078,7 @@ describe("list", () => {
|
|
|
1078
1078
|
"resource_packs/pack/pack_icon.png",
|
|
1079
1079
|
]);
|
|
1080
1080
|
});
|
|
1081
|
-
|
|
1081
|
+
it("warns about what a pruned listing left out", async () => {
|
|
1082
1082
|
const lines: string[] = [];
|
|
1083
1083
|
const noisy = createBridgeFiles({
|
|
1084
1084
|
root,
|
|
@@ -1109,7 +1109,7 @@ describe("list", () => {
|
|
|
1109
1109
|
"file listing skipped directories deeper than its depth limit",
|
|
1110
1110
|
]);
|
|
1111
1111
|
});
|
|
1112
|
-
|
|
1112
|
+
it("refuses unsafe listing roots", async () => {
|
|
1113
1113
|
await expect(
|
|
1114
1114
|
files.list("**/*", {
|
|
1115
1115
|
directory: "../outside",
|
|
@@ -1142,7 +1142,7 @@ describe("digest", () => {
|
|
|
1142
1142
|
});
|
|
1143
1143
|
});
|
|
1144
1144
|
|
|
1145
|
-
|
|
1145
|
+
it("answers with the known vectors for abc", async () => {
|
|
1146
1146
|
await Bun.write(`${root}/mods/alpha.jar`, "abc");
|
|
1147
1147
|
|
|
1148
1148
|
expect(await files.digest("mods/alpha.jar", "sha1")).toBe("a9993e364706816aba3e25717850c26c9cd0d89d");
|
|
@@ -1156,13 +1156,13 @@ describe("digest", () => {
|
|
|
1156
1156
|
);
|
|
1157
1157
|
});
|
|
1158
1158
|
|
|
1159
|
-
|
|
1159
|
+
it("answers with the known vector for an empty file", async () => {
|
|
1160
1160
|
await Bun.write(`${root}/empty.jar`, "");
|
|
1161
1161
|
|
|
1162
1162
|
expect(await files.digest("empty.jar", "sha1")).toBe("da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
|
1163
1163
|
});
|
|
1164
1164
|
|
|
1165
|
-
|
|
1165
|
+
it("hashes a file larger than one read chunk", async () => {
|
|
1166
1166
|
const content = "a".repeat(4 * 1024 * 1024 + 17);
|
|
1167
1167
|
|
|
1168
1168
|
await Bun.write(`${root}/big.bin`, content);
|
|
@@ -1170,14 +1170,14 @@ describe("digest", () => {
|
|
|
1170
1170
|
expect(await files.digest("big.bin", "sha256")).toBe(new Bun.CryptoHasher("sha256").update(content).digest("hex"));
|
|
1171
1171
|
});
|
|
1172
1172
|
|
|
1173
|
-
|
|
1173
|
+
it("refuses a path that is not a readable file", async () => {
|
|
1174
1174
|
await files.ensure("mods");
|
|
1175
1175
|
|
|
1176
1176
|
await expect(files.digest("mods", "sha1")).rejects.toThrow("is not a readable file");
|
|
1177
1177
|
await expect(files.digest("mods/missing.jar", "sha1")).rejects.toThrow("is not a readable file");
|
|
1178
1178
|
});
|
|
1179
1179
|
|
|
1180
|
-
|
|
1180
|
+
it("refuses a path outside the server directory", async () => {
|
|
1181
1181
|
await expect(files.digest("../escape.jar", "sha1")).rejects.toThrow("is not allowed");
|
|
1182
1182
|
});
|
|
1183
1183
|
});
|
|
@@ -1243,7 +1243,7 @@ describe("download", () => {
|
|
|
1243
1243
|
});
|
|
1244
1244
|
});
|
|
1245
1245
|
|
|
1246
|
-
|
|
1246
|
+
it("keeps a file the source undersized when the checksum passes", async () => {
|
|
1247
1247
|
const content = "a mod jar the index measured wrong";
|
|
1248
1248
|
|
|
1249
1249
|
await filesServing(content).download("mods/alpha.jar", "https://cdn.example.com/alpha.jar", {
|
|
@@ -1257,7 +1257,7 @@ describe("download", () => {
|
|
|
1257
1257
|
]);
|
|
1258
1258
|
});
|
|
1259
1259
|
|
|
1260
|
-
|
|
1260
|
+
it("refuses a file whose checksum does not match, however right its size", async () => {
|
|
1261
1261
|
const content = "a mod jar somebody swapped";
|
|
1262
1262
|
|
|
1263
1263
|
await expect(
|
|
@@ -1270,7 +1270,7 @@ describe("download", () => {
|
|
|
1270
1270
|
expect(await Bun.file(`${root}/mods/alpha.jar`).exists()).toBe(false);
|
|
1271
1271
|
});
|
|
1272
1272
|
|
|
1273
|
-
|
|
1273
|
+
it("holds an unverified file to its declared size", async () => {
|
|
1274
1274
|
const content = "a mod jar with no checksum to trust";
|
|
1275
1275
|
|
|
1276
1276
|
await expect(
|
|
@@ -1282,7 +1282,7 @@ describe("download", () => {
|
|
|
1282
1282
|
expect(await Bun.file(`${root}/mods/alpha.jar`).exists()).toBe(false);
|
|
1283
1283
|
});
|
|
1284
1284
|
|
|
1285
|
-
|
|
1285
|
+
it("holds an unverified file that stops short of its declared size", async () => {
|
|
1286
1286
|
const content = "a mod jar with no checksum to trust";
|
|
1287
1287
|
|
|
1288
1288
|
await expect(
|
|
@@ -1292,7 +1292,7 @@ describe("download", () => {
|
|
|
1292
1292
|
).rejects.toThrow(`returned ${Buffer.byteLength(content)} bytes, expected ${Buffer.byteLength(content) + 2}`);
|
|
1293
1293
|
});
|
|
1294
1294
|
|
|
1295
|
-
|
|
1295
|
+
it("carries the upstream status when the egress channel refuses", async () => {
|
|
1296
1296
|
const refusing: BridgeEgress = {
|
|
1297
1297
|
fetch() {
|
|
1298
1298
|
throw new Error("the egress channel is not part of this test");
|
|
@@ -1323,7 +1323,7 @@ describe("download", () => {
|
|
|
1323
1323
|
expect(thrown instanceof BridgeDownloadError ? thrown.status : null).toBe(429);
|
|
1324
1324
|
});
|
|
1325
1325
|
|
|
1326
|
-
|
|
1326
|
+
it("narrates a file it downloads unless the caller asks otherwise", async () => {
|
|
1327
1327
|
const content = "a server jar worth watching";
|
|
1328
1328
|
|
|
1329
1329
|
await filesServing(content).download("server.jar", "https://cdn.example.com/server.jar", {
|
|
@@ -1337,7 +1337,7 @@ describe("download", () => {
|
|
|
1337
1337
|
]);
|
|
1338
1338
|
});
|
|
1339
1339
|
|
|
1340
|
-
|
|
1340
|
+
it("says nothing about a quiet file", async () => {
|
|
1341
1341
|
const content = "a mod jar in a batch of hundreds";
|
|
1342
1342
|
|
|
1343
1343
|
await filesServing(content).download("mods/alpha.jar", "https://cdn.example.com/alpha.jar", {
|
|
@@ -1350,7 +1350,7 @@ describe("download", () => {
|
|
|
1350
1350
|
expect(messages).toEqual([]);
|
|
1351
1351
|
});
|
|
1352
1352
|
|
|
1353
|
-
|
|
1353
|
+
it("still warns and still fails on a quiet file", async () => {
|
|
1354
1354
|
const content = "a mod jar the index measured wrong";
|
|
1355
1355
|
|
|
1356
1356
|
await filesServing(content).download("mods/alpha.jar", "https://cdn.example.com/alpha.jar", {
|
|
@@ -1373,7 +1373,7 @@ describe("download", () => {
|
|
|
1373
1373
|
expect(messages).toEqual([]);
|
|
1374
1374
|
});
|
|
1375
1375
|
|
|
1376
|
-
|
|
1376
|
+
it("keeps no warning when the declared size is right", async () => {
|
|
1377
1377
|
const content = "a mod jar the index measured right";
|
|
1378
1378
|
|
|
1379
1379
|
await filesServing(content).download("mods/alpha.jar", "https://cdn.example.com/alpha.jar", {
|