Chore(deps): Update and remove dependencies (#3819)

## Description:

Only mentioning removals/major updates/notable changes below, not all
minor upgrades.

### Removed:
- "@aws-sdk/client-s3": not used anywhere (was used in Archive.ts
previously)
- chai, "@types/chai", sinon-chai: not used anywhere, probably leftover.
Vitest uses a bundled version of Chai for its expect asserations under
the hood too.
- protobufjs, "@types/google-protobuf": not used anywhere, probably left
from evan's experiment with it? Removed from vite.config.ts too.
- "@types/jquery": not used anywhere, probably leftover
- sinon, "@types/sinon": not used anywhere just like chai, probably
leftover. And Vitest provides us with the same functionality.
- "@types/systeminformation": dependency systeminformation was removed
last year, this is an unneeded, deprecated and unmaintained remainder.
- vite-tsconfig-paths: removed, and removed the import and usage in
vite.config.ts and replaced it by adding `tsconfigPaths: true` to the
`resolve` block. Because of this message displayed on running the tests:
"The plugin "vite-tsconfig-paths" is detected. Vite now supports
tsconfig paths resolution natively via the resolve.tsconfigPaths option.
You can remove the plugin and set resolve.tsconfigPaths: true in your
Vite config instead."
- vite-plugin-static-copy: removed, we don't use it anymore (was used in
our vite.config.ts once,, probably before Vite natively supported
copying static assets via its publicDir configuration)

### Updated:
- color.js: v0.5 > v0.6, no breaking change affecting us
- cross-env: v7 > v10. It's a publicly archived repo since Nov 2025. But
before that he got it up-to-date from June 2025, porting to TS, dropping
old Node versions, dependencies etc. Seems still good to use for some
amount of time to come.
- dotenv: v16 > v17, now logs an informational message by default when
it loads an environment file. Can be disabled by using
dotenv.config({quite: true}) if needed.
- ejs: v3 > v5: security patches mostly. Vite still uses v3 btw.
- eslint: v9 > v10. Newly enabled rules by default:
'no-unassigned-vars', 'no-useless-assignment' and
'preserve-caught-error'. Mostly faster and minimum support moved to
higher node versions, which shouldn't be a problem.
- "@eslint/compat": v1 > v2. Minimum supported Node versions, which
should not be a problem.
- intl-messageformat: v10 > v11 no breaking changes that affect us
- jdom: v27 > v29. Faster. Most notably minimum support moved to higher
node v22 version, which should not be a problem. Also, see types/node,
kind of expecting v24 to be installed now.
- nanoid: from v3 to v5, no breaking changes that affect us
- "@opentelemetry/sdk-logs": now that addLogRecordProcessor is removed,
changed Logger.ts to pass an (empty) provider array directly to the
LoggerProvider constructor. Follows the changes in
https://github.com/open-telemetry/opentelemetry-js/pull/5588
- "@tailwindcss/vite": supports vite v8 from 4.2.2, and a fix for it in
4.2.4
- tailwindcss: supports vite v8 from 4.2.2
-- in 4.1.15 (we were already above this version) break-words was
deprecated in favor of wrap-break-word. But break-words, which we use in
15 places, will still work as expected
(https://github.com/tailwindlabs/tailwindcss/pull/19157). Same goes for
also deprecated "order-none".
- "@types/node": from v22 to v24, assuming most now use node 24
- vite v7 > v8: 
-- is now on 8.0.10 so first bugs are out of it, while v8 itself also
fixed a big number of bugs.
-- in vite.config.ts, fixed Ts error/compilation issue by changing the
manualChunks option in build.rollupOptions.output to use the function
syntax, which is required by the updated types instead of the object
syntax.
- zod: no changes that affect us

### Prettier:
Updated only because of (new because of update?) Prettier errors for
files untouched in this PR originally:
- PathFinder.Parabola.ts
- WorkerMessages.ts
- ClanModal.handlers.test.ts
- ClanModal.rendering.test.ts‎
- CONTRIBUTING.md
- README.md

### ESLint:
Fixes needed to silence errors coming from newly enabled recommended
rules 'no-useless-assignment' and 'preserve-caught-error':

For 'no-useless-assignment' (default assignment never used because of
unreachable code or they are guaranteed to get a value, so they can be
undefinedat the start. Exception was AttackExecution, so made the
default value of 0 the default case in the switch statement):
- ClientGameRunner
- GameModeSelector
- NameBoxCalculator
- StructureDrawingUtils
- TerritoryLayer
- Diagnostics
- GameRunner
- ColorAllocator
- DefaultConfig
- AttackExecution
- AiAttackBehavior
- Worker.worker
- GamePreviewBuilder

For 'preserve-caught-error', disabled the rule here because the possible
fix `{cause: error}` was introduced in ES2022 while we're still on
target ES2020 currently:
- GameServer
- Privilege

_Error: The value assigned to 'gameMap' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'timeDisplay' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'scalingFactor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'radius' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'teamColor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'gl' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'power' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'tickExecutionDuration' is not used in
subsequent statements. (no-useless-assignment)
Error: The value assigned to 'selectedIndex' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'mag' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'speed' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'matchesCriteria' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'shouldContinue' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'description' is not used in subsequent
statements. (no-useless-assignment)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)_

All tests pass. TypeScript and ESLint errors resolved.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

tryout33

---------

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
VariableVince
2026-05-06 09:12:27 -06:00
committed by GitHub
co-authored by Copilot
parent c0b976448b
commit eca5794ebb
25 changed files with 1910 additions and 5015 deletions
-1
View File
@@ -101,7 +101,6 @@ All new features and bug fixes should include relevant tests. We use **Vitest**.
## Submitting a Pull Request ## Submitting a Pull Request
1. **Commit your changes**: 1. **Commit your changes**:
- Write clear, concise commit messages. - Write clear, concise commit messages.
- Use the present tense ("Add feature" not "Added feature"). - Use the present tense ("Add feature" not "Added feature").
-4
View File
@@ -181,7 +181,6 @@ Feel free to ask questions in the translation Discord server!
To ensure code quality and project stability, we use a progressive contribution system: To ensure code quality and project stability, we use a progressive contribution system:
1. **New Contributors**: Limited to UI improvements and small bug fixes only 1. **New Contributors**: Limited to UI improvements and small bug fixes only
- This helps you become familiar with the codebase - This helps you become familiar with the codebase
- UI changes are easier to review and less likely to break core functionality - UI changes are easier to review and less likely to break core functionality
- Small, focused PRs have a higher chance of being accepted - Small, focused PRs have a higher chance of being accepted
@@ -193,20 +192,17 @@ To ensure code quality and project stability, we use a progressive contribution
### How to Contribute Successfully ### How to Contribute Successfully
1. **Before Starting Work**: 1. **Before Starting Work**:
- Open an issue describing what you want to contribute - Open an issue describing what you want to contribute
- Wait for maintainer feedback before investing significant time - Wait for maintainer feedback before investing significant time
- Small improvements can proceed directly to PR stage - Small improvements can proceed directly to PR stage
2. **Code Quality Requirements**: 2. **Code Quality Requirements**:
- All code must be well-commented and follow existing style patterns - All code must be well-commented and follow existing style patterns
- New features should not break existing functionality - New features should not break existing functionality
- Code should be thoroughly tested before submission - Code should be thoroughly tested before submission
- All code changes in src/core _MUST_ be tested. - All code changes in src/core _MUST_ be tested.
3. **Pull Request Process**: 3. **Pull Request Process**:
- Keep PRs focused on a single feature or bug fix - Keep PRs focused on a single feature or bug fix
- Include screenshots for UI changes - Include screenshots for UI changes
- Describe what testing you've performed - Describe what testing you've performed
+1799 -4872
View File
File diff suppressed because it is too large Load Diff
+52 -64
View File
@@ -29,103 +29,91 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@datastructures-js/priority-queue": "^6.3.3", "@datastructures-js/priority-queue": "^6.3.5",
"@eslint/compat": "^1.2.7", "@eslint/compat": "^2.0.5",
"@eslint/js": "^9.21.0", "@eslint/js": "^10.0.1",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.2.4",
"@types/benchmark": "^2.1.5", "@types/benchmark": "^2.1.5",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"@types/ejs": "^3.1.5", "@types/ejs": "^3.1.5",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/google-protobuf": "^3.15.12",
"@types/hammerjs": "^2.0.46", "@types/hammerjs": "^2.0.46",
"@types/howler": "^2.2.12", "@types/howler": "^2.2.12",
"@types/jquery": "^3.5.31",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/msgpack5": "^3.4.6", "@types/msgpack5": "^3.4.6",
"@types/node": "^22.10.2", "@types/node": "^24.12.0",
"@types/pg": "^8.11.11", "@types/pg": "^8.20.0",
"@types/seedrandom": "^3.0.8", "@types/seedrandom": "^3.0.8",
"@types/sinon": "^17.0.3", "@types/ws": "^8.18.1",
"@types/systeminformation": "^3.23.1", "@vitest/coverage-v8": "^4.1.5",
"@types/ws": "^8.5.11", "@vitest/ui": "^4.1.5",
"@vitest/coverage-v8": "^4.0.16", "autoprefixer": "^10.5.0",
"@vitest/ui": "^4.0.16",
"autoprefixer": "^10.4.20",
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"canvas": "^3.2.1", "canvas": "^3.2.3",
"chai": "^5.1.1",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"cross-env": "^7.0.3", "cross-env": "^10.1.0",
"d3": "^7.9.0", "d3": "^7.9.0",
"eslint": "^9.21.0", "eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.1", "eslint-config-prettier": "^10.1.8",
"eslint-formatter-gha": "^1.5.2", "eslint-formatter-gha": "^2.0.1",
"glob": "^13.0.0", "glob": "^13.0.6",
"globals": "^16.0.0", "globals": "^17.6.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jsdom": "^27.4.0", "jsdom": "^29.1.1",
"lint-staged": "^16.1.2", "lint-staged": "^16.4.0",
"lit": "^3.3.1", "lit": "^3.3.2",
"lit-markdown": "^1.3.2", "lit-markdown": "^1.3.2",
"mrmime": "^2.0.0", "mrmime": "^2.0.1",
"pixi-filters": "^6.1.5", "pixi-filters": "^6.1.5",
"pixi.js": "^8.18.1", "pixi.js": "^8.18.1",
"prettier": "^3.5.3", "prettier": "^3.8.3",
"prettier-plugin-organize-imports": "^4.1.0", "prettier-plugin-organize-imports": "^4.3.0",
"prettier-plugin-sh": "^0.17.4", "prettier-plugin-sh": "^0.18.1",
"protobufjs": "^7.5.5", "tailwindcss": "^4.2.4",
"sinon": "^21.0.1",
"sinon-chai": "^4.0.0",
"tailwindcss": "^4.1.18",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.59.1", "typescript-eslint": "^8.59.1",
"vite": "^7.3.2", "vite": "^8.0.10",
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "^3.2.2",
"vite-plugin-static-copy": "^3.1.4", "vitest": "^4.1.5",
"vite-tsconfig-paths": "^6.0.3", "vitest-canvas-mock": "^1.1.4"
"vitest": "^4.0.16",
"vitest-canvas-mock": "^1.1.3"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.758.0",
"@lit-labs/virtualizer": "^2.1.1", "@lit-labs/virtualizer": "^2.1.1",
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.1",
"@opentelemetry/api-logs": "^0.200.0", "@opentelemetry/api-logs": "^0.216.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.200.0", "@opentelemetry/exporter-logs-otlp-http": "^0.216.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.200.0", "@opentelemetry/exporter-metrics-otlp-http": "^0.216.0",
"@opentelemetry/resources": "^2.0.0", "@opentelemetry/resources": "^2.7.1",
"@opentelemetry/sdk-logs": "^0.200.0", "@opentelemetry/sdk-logs": "^0.216.0",
"@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-metrics": "^2.7.1",
"@opentelemetry/semantic-conventions": "^1.32.0", "@opentelemetry/semantic-conventions": "^1.40.0",
"@opentelemetry/winston-transport": "^0.11.0", "@opentelemetry/winston-transport": "^0.26.0",
"@types/compression": "^1.8.1", "@types/compression": "^1.8.1",
"colord": "^2.9.3", "colord": "^2.9.3",
"colorjs.io": "^0.5.2", "colorjs.io": "^0.6.1",
"compression": "^1.8.1", "compression": "^1.8.1",
"dompurify": "^3.4.0", "dompurify": "^3.4.2",
"dotenv": "^16.5.0", "dotenv": "^17.4.2",
"ejs": "^3.1.10", "ejs": "^5.0.2",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.3.2", "express-rate-limit": "^8.4.1",
"fastpriorityqueue": "^0.7.5", "fastpriorityqueue": "^0.8.0",
"howler": "^2.2.4", "howler": "^2.2.4",
"intl-messageformat": "^10.7.16", "intl-messageformat": "^11.2.3",
"ip-anonymize": "^0.1.0", "ip-anonymize": "^0.1.0",
"jose": "^6.0.10", "jose": "^6.2.3",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"limiter": "^3.0.0", "limiter": "^3.0.0",
"nanoid": "^3.3.6", "nanoid": "^5.1.11",
"node-html-parser": "^7.0.2", "node-html-parser": "^7.1.0",
"obscenity": "^0.4.3", "obscenity": "^0.4.6",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsx": "^4.17.0", "tsx": "^4.21.0",
"winston": "^3.17.0", "winston": "^3.19.0",
"ws": "^8.18.0", "ws": "^8.20.0",
"zod": "^4.0.5" "zod": "^4.4.2"
}, },
"type": "module" "type": "module"
} }
+1 -1
View File
@@ -237,7 +237,7 @@ async function createClientGame(
userSettings, userSettings,
lobbyConfig.gameRecord !== undefined, lobbyConfig.gameRecord !== undefined,
); );
let gameMap: TerrainMapData | null = null; let gameMap: TerrainMapData;
if (terrainLoad) { if (terrainLoad) {
gameMap = await terrainLoad; gameMap = await terrainLoad;
+1 -1
View File
@@ -275,7 +275,7 @@ export class GameModeSelector extends LitElement {
? getSecondsUntilServerTimestamp(lobby.startsAt, this.serverTimeOffset) ? getSecondsUntilServerTimestamp(lobby.startsAt, this.serverTimeOffset)
: undefined; : undefined;
let timeDisplay: string = ""; let timeDisplay: string;
let timeDisplayUppercase = false; let timeDisplayUppercase = false;
if (timeRemaining === undefined) { if (timeRemaining === undefined) {
timeDisplay = renderDuration(this.defaultLobbyTime); timeDisplay = renderDuration(this.defaultLobbyTime);
+1 -1
View File
@@ -18,7 +18,7 @@ export function placeName(game: Game, player: Player): NameViewData {
player.largestClusterBoundingBox ?? player.largestClusterBoundingBox ??
calculateBoundingBox(game, player.borderTiles()); calculateBoundingBox(game, player.borderTiles());
let scalingFactor = 1; let scalingFactor: number;
const width = boundingBox.max.x - boundingBox.min.x; const width = boundingBox.max.x - boundingBox.min.x;
const height = boundingBox.max.y - boundingBox.min.y; const height = boundingBox.max.y - boundingBox.min.y;
const size = Math.min(width, height); const size = Math.min(width, height);
@@ -489,7 +489,7 @@ export class SpriteFactory {
if (stage === undefined) throw new Error("Not initialized"); if (stage === undefined) throw new Error("Not initialized");
const parentContainer = new PIXI.Container(); const parentContainer = new PIXI.Container();
const circle = new PIXI.Graphics(); const circle = new PIXI.Graphics();
let radius = 0; let radius: number;
switch (type) { switch (type) {
case UnitType.SAMLauncher: case UnitType.SAMLauncher:
radius = this.game.config().samRange(level ?? 1); radius = this.game.config().samRange(level ?? 1);
+1 -1
View File
@@ -244,7 +244,7 @@ export class TerritoryLayer implements Layer {
minRad + (maxRad - minRad) * (0.5 + 0.5 * Math.sin(this.borderAnimTime)); minRad + (maxRad - minRad) * (0.5 + 0.5 * Math.sin(this.borderAnimTime));
const baseColor = this.theme.spawnHighlightSelfColor(); //white const baseColor = this.theme.spawnHighlightSelfColor(); //white
let teamColor: Colord | null = null; let teamColor: Colord;
const team: Team | null = focusedPlayer.team(); const team: Team | null = focusedPlayer.team();
if (team !== null && Object.values(ColoredTeams).includes(team)) { if (team !== null && Object.values(ColoredTeams).includes(team)) {
+2 -3
View File
@@ -63,10 +63,9 @@ export async function collectGraphicsDiagnostics(
/* ---------- Rendering ---------- */ /* ---------- Rendering ---------- */
let gl: WebGLRenderingContext | WebGL2RenderingContext | null = null;
let type: RendererType = "Canvas2D"; let type: RendererType = "Canvas2D";
gl = const gl =
canvas.getContext("webgl2", { antialias: true }) ?? canvas.getContext("webgl2", { antialias: true }) ??
canvas.getContext("webgl", { antialias: true }); canvas.getContext("webgl", { antialias: true });
@@ -111,7 +110,7 @@ export async function collectGraphicsDiagnostics(
/* ---------- Power ---------- */ /* ---------- Power ---------- */
let power: PowerInfo = {}; let power: PowerInfo;
if ("getBattery" in navigator) { if ("getBattery" in navigator) {
try { try {
+1 -1
View File
@@ -131,7 +131,7 @@ export class GameRunner {
this.currTurn++; this.currTurn++;
let updates: GameUpdates; let updates: GameUpdates;
let tickExecutionDuration: number = 0; let tickExecutionDuration: number;
try { try {
const startTime = performance.now(); const startTime = performance.now();
+1 -1
View File
@@ -65,7 +65,7 @@ export class ColorAllocator {
this.availableColors = [...this.fallbackColors]; this.availableColors = [...this.fallbackColors];
} }
let selectedIndex = 0; let selectedIndex: number;
if (this.assigned.size === 0 || this.assigned.size > 50) { if (this.assigned.size === 0 || this.assigned.size > 50) {
// Randomly pick the first color if no colors have been assigned yet. // Randomly pick the first color if no colors have been assigned yet.
+2 -2
View File
@@ -635,8 +635,8 @@ export class DefaultConfig implements Config {
defenderTroopLoss: number; defenderTroopLoss: number;
tilesPerTickUsed: number; tilesPerTickUsed: number;
} { } {
let mag = 0; let mag: number;
let speed = 0; let speed: number;
const type = gm.terrainType(tileToConquer); const type = gm.terrainType(tileToConquer);
switch (type) { switch (type) {
case TerrainType.Plains: case TerrainType.Plains:
+4 -1
View File
@@ -338,7 +338,7 @@ export class AttackExecution implements Execution {
} }
} }
let mag = 0; let mag: number;
switch (this.mg.terrainType(neighbor)) { switch (this.mg.terrainType(neighbor)) {
case TerrainType.Plains: case TerrainType.Plains:
mag = 1; mag = 1;
@@ -349,6 +349,9 @@ export class AttackExecution implements Execution {
case TerrainType.Mountain: case TerrainType.Mountain:
mag = 2; mag = 2;
break; break;
default:
mag = 0;
break;
} }
const priority = const priority =
+1 -1
View File
@@ -183,7 +183,7 @@ export class AiAttackBehavior {
continue; continue;
} }
let matchesCriteria = false; let matchesCriteria: boolean;
if (highInterestOnly) { if (highInterestOnly) {
// High-interest targeting: prioritize unowned tiles or tiles owned by bots // High-interest targeting: prioritize unowned tiles or tiles owned by bots
matchesCriteria = !owner.isPlayer() || owner.type() === PlayerType.Bot; matchesCriteria = !owner.isPlayer() || owner.type() === PlayerType.Bot;
+1 -3
View File
@@ -11,9 +11,7 @@ export interface ParabolaOptions {
const PARABOLA_MIN_HEIGHT = 50; const PARABOLA_MIN_HEIGHT = 50;
export class ParabolaUniversalPathFinder export class ParabolaUniversalPathFinder implements SteppingPathFinder<TileRef> {
implements SteppingPathFinder<TileRef>
{
private curve: DistanceBasedBezierCurve | null = null; private curve: DistanceBasedBezierCurve | null = null;
private lastTo: TileRef | null = null; private lastTo: TileRef | null = null;
+1 -1
View File
@@ -50,7 +50,7 @@ async function drain(): Promise<void> {
draining = true; draining = true;
drainRequested = false; drainRequested = false;
let shouldContinue = false; let shouldContinue: boolean;
try { try {
const gr = await gameRunner; const gr = await gameRunner;
if (!gr) { if (!gr) {
+1 -2
View File
@@ -122,8 +122,7 @@ export interface AttackClusteredPositionsMessage extends BaseWorkerMessage {
attackID?: string; attackID?: string;
} }
export interface AttackClusteredPositionsResultMessage export interface AttackClusteredPositionsResultMessage extends BaseWorkerMessage {
extends BaseWorkerMessage {
type: "attack_clustered_positions_result"; type: "attack_clustered_positions_result";
attacks: { id: string; positions: { x: number; y: number }[] }[]; attacks: { id: string; positions: { x: number; y: number }[] }[];
} }
+1 -1
View File
@@ -210,7 +210,7 @@ export async function buildPreview(
? `${mode} on ${map}${gameTypeLabel}` ? `${mode} on ${map}${gameTypeLabel}`
: "OpenFront Game"; : "OpenFront Game";
let description = ""; let description: string;
if (isFinished) { if (isFinished) {
const parts: string[] = []; const parts: string[] = [];
if (winner) { if (winner) {
+2
View File
@@ -794,6 +794,8 @@ export class GameServer {
} satisfies ServerStartGameMessage), } satisfies ServerStartGameMessage),
); );
} catch (error) { } catch (error) {
// can be enabled once we can use {cause: error} in Error constructor starting with ES2022
// eslint-disable-next-line preserve-caught-error
throw new Error( throw new Error(
`error sending start message for game ${this.id}, ${error}`.substring( `error sending start message for game ${this.id}, ${error}`.substring(
0, 0,
+5 -9
View File
@@ -15,11 +15,6 @@ const config = getServerConfigFromServer();
const resource = getOtelResource(); const resource = getOtelResource();
// Initialize the OpenTelemetry Logger Provider
const loggerProvider = new LoggerProvider({
resource,
});
if (config.otelEnabled()) { if (config.otelEnabled()) {
console.log("OTEL enabled"); console.log("OTEL enabled");
// Configure OpenTelemetry endpoint with basic auth (if provided) // Configure OpenTelemetry endpoint with basic auth (if provided)
@@ -31,10 +26,11 @@ if (config.otelEnabled()) {
headers, headers,
}); });
// Add a log processor with the exporter // Initialize the OpenTelemetry Logger Provider
loggerProvider.addLogRecordProcessor( const loggerProvider = new LoggerProvider({
new SimpleLogRecordProcessor(logExporter), resource,
); processors: [new SimpleLogRecordProcessor(logExporter)],
});
// Set as the global logger provider // Set as the global logger provider
logsAPI.logs.setGlobalLoggerProvider(loggerProvider); logsAPI.logs.setGlobalLoggerProvider(loggerProvider);
+2
View File
@@ -212,6 +212,8 @@ export class PrivilegeCheckerImpl implements PrivilegeChecker {
try { try {
decodePatternData(found.pattern, this.b64urlDecode); decodePatternData(found.pattern, this.b64urlDecode);
} catch (e) { } catch (e) {
// can be enabled once we can use {cause: error} in Error constructor starting with ES2022
// eslint-disable-next-line preserve-caught-error
throw new Error(`Invalid pattern ${name}`); throw new Error(`Invalid pattern ${name}`);
} }
+22 -33
View File
@@ -53,9 +53,8 @@ describe("ClanModal — handlers", () => {
describe("handleApprove increments selectedClan.memberCount", () => { describe("handleApprove increments selectedClan.memberCount", () => {
it("increments memberCount by 1 after successful approveClanRequest", async () => { it("increments memberCount by 1 after successful approveClanRequest", async () => {
const { approveClanRequest, fetchClanRequests } = await import( const { approveClanRequest, fetchClanRequests } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true); (approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true);
(fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
results: [ results: [
@@ -90,9 +89,8 @@ describe("ClanModal — handlers", () => {
}); });
it("does not increment memberCount when approveClanRequest fails", async () => { it("does not increment memberCount when approveClanRequest fails", async () => {
const { approveClanRequest, fetchClanRequests } = await import( const { approveClanRequest, fetchClanRequests } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue({ (approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue({
error: "clan_modal.error_generic", error: "clan_modal.error_generic",
}); });
@@ -125,9 +123,8 @@ describe("ClanModal — handlers", () => {
}); });
it("treats undefined memberCount as 0 and increments to 1", async () => { it("treats undefined memberCount as 0 and increments to 1", async () => {
const { approveClanRequest, fetchClanRequests } = await import( const { approveClanRequest, fetchClanRequests } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true); (approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true);
(fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
results: [ results: [
@@ -271,9 +268,8 @@ describe("ClanModal — handlers", () => {
}); });
it("handleBan syncs memberCount via clan-updated event on success", async () => { it("handleBan syncs memberCount via clan-updated event on success", async () => {
const { banClanMember, fetchClanMembers } = await import( const { banClanMember, fetchClanMembers } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(banClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true); (banClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
// Server returns the post-ban member total (was 5, now 4). // Server returns the post-ban member total (was 5, now 4).
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
@@ -310,9 +306,8 @@ describe("ClanModal — handlers", () => {
describe("handleUnban", () => { describe("handleUnban", () => {
it("removes ban from list and decrements total on success", async () => { it("removes ban from list and decrements total on success", async () => {
const { unbanClanMember, fetchClanBans } = await import( const { unbanClanMember, fetchClanBans } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(unbanClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true); (unbanClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
(fetchClanBans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (fetchClanBans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
results: [ results: [
@@ -377,9 +372,8 @@ describe("ClanModal — handlers", () => {
}); });
it("calls kickMember and syncs memberCount on success", async () => { it("calls kickMember and syncs memberCount on success", async () => {
const { kickMember, fetchClanMembers } = await import( const { kickMember, fetchClanMembers } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(kickMember as ReturnType<typeof vi.fn>).mockResolvedValue(true); (kickMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
results: [], results: [],
@@ -411,9 +405,8 @@ describe("ClanModal — handlers", () => {
}); });
it("does not mutate state when kickMember fails", async () => { it("does not mutate state when kickMember fails", async () => {
const { kickMember, fetchClanMembers } = await import( const { kickMember, fetchClanMembers } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(kickMember as ReturnType<typeof vi.fn>).mockResolvedValue({ (kickMember as ReturnType<typeof vi.fn>).mockResolvedValue({
error: "clan_modal.error_generic", error: "clan_modal.error_generic",
}); });
@@ -600,9 +593,8 @@ describe("ClanModal — handlers", () => {
describe("handleJoin", () => { describe("handleJoin", () => {
beforeEach(async () => { beforeEach(async () => {
const { fetchClanDetail, fetchClanStats } = await import( const { fetchClanDetail, fetchClanStats } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce( (fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
makeClan({ isOpen: true, memberCount: 5 }), makeClan({ isOpen: true, memberCount: 5 }),
); );
@@ -615,9 +607,8 @@ describe("ClanModal — handlers", () => {
}); });
it("switches detail view into member mode immediately after open-clan join", async () => { it("switches detail view into member mode immediately after open-clan join", async () => {
const { joinClan, fetchClanMembers } = await import( const { joinClan, fetchClanMembers } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(joinClan as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (joinClan as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
status: "joined", status: "joined",
}); });
@@ -781,9 +772,8 @@ describe("ClanModal — handlers", () => {
}); });
it("clears confirmAction and removes the dialog after confirming", async () => { it("clears confirmAction and removes the dialog after confirming", async () => {
const { transferLeadership } = await import( const { transferLeadership } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(transferLeadership as ReturnType<typeof vi.fn>).mockResolvedValue(true); (transferLeadership as ReturnType<typeof vi.fn>).mockResolvedValue(true);
const dialog = modal.querySelector("confirm-dialog"); const dialog = modal.querySelector("confirm-dialog");
@@ -802,9 +792,8 @@ describe("ClanModal — handlers", () => {
}); });
it("clears confirmAction when cancel is clicked, without calling the API", async () => { it("clears confirmAction when cancel is clicked, without calling the API", async () => {
const { transferLeadership } = await import( const { transferLeadership } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
const dialog = modal.querySelector("confirm-dialog"); const dialog = modal.querySelector("confirm-dialog");
expect(dialog).toBeTruthy(); expect(dialog).toBeTruthy();
@@ -241,9 +241,8 @@ describe("ClanModal — rendering", () => {
}); });
it("shows 0 in the stats row of the detail view when memberCount is undefined", async () => { it("shows 0 in the stats row of the detail view when memberCount is undefined", async () => {
const { fetchClanDetail, fetchClanStats } = await import( const { fetchClanDetail, fetchClanStats } =
"../../../src/client/ClanApi" await import("../../../src/client/ClanApi");
);
(fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce( (fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
makeClan({ memberCount: undefined }), makeClan({ memberCount: undefined }),
); );
+6 -8
View File
@@ -5,7 +5,6 @@ import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import { defineConfig, loadEnv, type Plugin } from "vite"; import { defineConfig, loadEnv, type Plugin } from "vite";
import { createHtmlPlugin } from "vite-plugin-html"; import { createHtmlPlugin } from "vite-plugin-html";
import tsconfigPaths from "vite-tsconfig-paths";
import { import {
type AssetManifest, type AssetManifest,
buildAssetUrl, buildAssetUrl,
@@ -155,17 +154,13 @@ export default defineConfig(({ mode }) => {
publicDir: isProduction ? false : "resources", publicDir: isProduction ? false : "resources",
resolve: { resolve: {
tsconfigPaths: true,
alias: { alias: {
"protobufjs/minimal": path.resolve(
__dirname,
"node_modules/protobufjs/minimal.js",
),
resources: path.resolve(__dirname, "resources"), resources: path.resolve(__dirname, "resources"),
}, },
}, },
plugins: [ plugins: [
tsconfigPaths(),
...(!isProduction ...(!isProduction
? [serveProprietaryDir(proprietaryDir, resourcesDir)] ? [serveProprietaryDir(proprietaryDir, resourcesDir)]
: []), : []),
@@ -209,8 +204,11 @@ export default defineConfig(({ mode }) => {
assetsDir: "assets", // Sub-directory for assets assetsDir: "assets", // Sub-directory for assets
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: (id) => {
vendor: ["pixi.js", "howler", "zod", "protobufjs"], const vendorModules = ["pixi.js", "howler", "zod"];
if (vendorModules.some((module) => id.includes(module))) {
return "vendor";
}
}, },
}, },
}, },