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