v0.5.2 — Add ESLint + typescript-eslint, fix lint errors

Add flat-config ESLint with typescript-eslint recommended rules,
fix all lint errors (unused imports/vars, empty catches, Function types,
prefer-const, useless assignments), and rename stale kpc- CSS class to kcc-.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 09:24:34 -08:00
parent b8bfa2941c
commit 1eabea195a
12 changed files with 956 additions and 47 deletions
+19
View File
@@ -0,0 +1,19 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
ignores: ["dist/", "out/", "scripts/"],
},
{
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
},
}
);
+911 -25
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "krunker-civilian-client", "name": "krunker-civilian-client",
"version": "0.5.1", "version": "0.5.2",
"description": "Cross-platform Krunker game client", "description": "Cross-platform Krunker game client",
"main": "dist/main/index.js", "main": "dist/main/index.js",
"homepage": "https://gitea.crjlab.net/bigjakk/krunker-civilian-client", "homepage": "https://gitea.crjlab.net/bigjakk/krunker-civilian-client",
@@ -17,18 +17,22 @@
"dist:win": "npm run build && electron-builder --win", "dist:win": "npm run build && electron-builder --win",
"dist:linux": "npm run build && electron-builder --linux", "dist:linux": "npm run build && electron-builder --linux",
"dist:all": "npm run build && electron-builder --win --linux", "dist:all": "npm run build && electron-builder --win --linux",
"clean": "rimraf dist out" "clean": "rimraf dist out",
"lint": "eslint src/"
}, },
"dependencies": { "dependencies": {
"electron-store": "^8.2.0", "electron-store": "^8.2.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"electron": "npm:electron-nightly@42.0.0-nightly.20260227", "electron": "npm:electron-nightly@42.0.0-nightly.20260227",
"electron-builder": "^26.0.0", "electron-builder": "^26.0.0",
"eslint": "^10.0.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"typescript": "^5.7.0", "typescript": "^5.7.0",
"typescript-eslint": "^8.56.1",
"vite": "^6.0.0" "vite": "^6.0.0"
} }
} }
+1 -1
View File
@@ -518,7 +518,7 @@ export const MATCHMAKER_SETTINGS_CSS = `
`; `;
export const TRANSLATOR_CSS = ` export const TRANSLATOR_CSS = `
.kpc-translation { .kcc-translation {
color: #88ff88; color: #88ff88;
font-style: italic; font-style: italic;
margin-left: 8px; margin-left: 8px;
+4 -5
View File
@@ -1,4 +1,4 @@
import { app, BrowserWindow, Menu, clipboard, dialog, ipcMain, protocol, safeStorage, session, shell } from 'electron'; import { app, BrowserWindow, Menu, clipboard, dialog, ipcMain, safeStorage, session, shell } from 'electron';
import { join } from 'path'; import { join } from 'path';
import { existsSync, mkdirSync, promises as fsp } from 'fs'; import { existsSync, mkdirSync, promises as fsp } from 'fs';
import { get as httpsGet } from 'https'; import { get as httpsGet } from 'https';
@@ -14,6 +14,7 @@ import { showUpdateWindow } from './update-window';
import { DiscordRPC } from './discord-rpc'; import { DiscordRPC } from './discord-rpc';
// ── App version for API calls ── // ── App version for API calls ──
// eslint-disable-next-line @typescript-eslint/no-require-imports
const appVersion: string = require('../../package.json').version; const appVersion: string = require('../../package.json').version;
// ── Region ping cache ── // ── Region ping cache ──
@@ -247,7 +248,7 @@ async function launchApp(): Promise<void> {
try { try {
const host = new URL(details.url).hostname; const host = new URL(details.url).hostname;
if (host.endsWith('krunker.io')) return callback({}); if (host.endsWith('krunker.io')) return callback({});
} catch {} } catch { /* ignore invalid URLs */ }
// Otherwise it matched an ad-block pattern — cancel it // Otherwise it matched an ad-block pattern — cancel it
callback({ cancel: true }); callback({ cancel: true });
}); });
@@ -317,8 +318,6 @@ async function launchApp(): Promise<void> {
} }
// ── Common output directory (used by folder actions) ── // ── Common output directory (used by folder actions) ──
const outputDir = join(app.getPath('documents'), 'Krunker Civilian Client');
// ── Configurable keybinds via before-input-event ── // ── Configurable keybinds via before-input-event ──
win.webContents.on('before-input-event', (event, input) => { win.webContents.on('before-input-event', (event, input) => {
if (input.type !== 'keyDown') return; if (input.type !== 'keyDown') return;
@@ -333,7 +332,7 @@ async function launchApp(): Promise<void> {
event.preventDefault(); event.preventDefault();
} else if (matchesKeybind(input, binds.joinFromClipboard)) { } else if (matchesKeybind(input, binds.joinFromClipboard)) {
const text = clipboard.readText(); const text = clipboard.readText();
try { const u = new URL(text); if (u.protocol === 'https:' && u.hostname.endsWith('krunker.io')) win.loadURL(text); } catch {}; try { const u = new URL(text); if (u.protocol === 'https:' && u.hostname.endsWith('krunker.io')) win.loadURL(text); } catch { /* ignore invalid URLs */ }
event.preventDefault(); event.preventDefault();
} else if (matchesKeybind(input, binds.copyGameLink)) { } else if (matchesKeybind(input, binds.copyGameLink)) {
clipboard.writeText(win.webContents.getURL()); clipboard.writeText(win.webContents.getURL());
+1 -1
View File
@@ -67,7 +67,7 @@ function makeLogger(getStream: () => WriteStream) {
export const electronLog = makeLogger(() => electronStream); export const electronLog = makeLogger(() => electronStream);
export function getLogPath(type: 'electron'): string { export function getLogPath(_type: 'electron'): string {
init(); init();
return electronPath; return electronPath;
} }
+1 -1
View File
@@ -87,7 +87,7 @@ export class ResourceSwapper {
this.swapFiles.set(name, join(this.swapDir, name)); this.swapFiles.set(name, join(this.swapDir, name));
} }
} }
} catch (err) { } catch {
console.error(`Failed to scan swap directory prefix: ${prefix}`); console.error(`Failed to scan swap directory prefix: ${prefix}`);
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { get as httpsGet, request as httpsRequest } from 'https'; import { get as httpsGet } from 'https';
import { createWriteStream, renameSync, unlinkSync, existsSync } from 'fs'; import { createWriteStream, renameSync, unlinkSync, existsSync } from 'fs';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { app } from 'electron'; import { app } from 'electron';
+1 -1
View File
@@ -49,7 +49,7 @@ export class UserscriptManager {
/** Load tracker.json, add new scripts as disabled, prune deleted scripts */ /** Load tracker.json, add new scripts as disabled, prune deleted scripts */
async loadTracker(scripts: ScriptFile[]): Promise<ScriptTracker> { async loadTracker(scripts: ScriptFile[]): Promise<ScriptTracker> {
let tracker: ScriptTracker = {}; let tracker: ScriptTracker;
try { try {
tracker = JSON.parse(await fsp.readFile(this.trackerPath, 'utf-8')); tracker = JSON.parse(await fsp.readFile(this.trackerPath, 'utf-8'));
} catch { tracker = {}; } } catch { tracker = {}; }
+7 -7
View File
@@ -2,7 +2,7 @@ import { ipcRenderer } from 'electron';
import { fetchGame, MATCHMAKER_GAMEMODES, MATCHMAKER_REGIONS, MATCHMAKER_REGION_NAMES } from './matchmaker'; import { fetchGame, MATCHMAKER_GAMEMODES, MATCHMAKER_REGIONS, MATCHMAKER_REGION_NAMES } from './matchmaker';
import type { MatchmakerConfig } from './matchmaker'; import type { MatchmakerConfig } from './matchmaker';
import { initUserscripts, getInstances, setScriptEnabled } from './userscripts'; import { initUserscripts, getInstances, setScriptEnabled } from './userscripts';
import type { UserscriptInstance, UserscriptSetting } from './userscripts'; import type { UserscriptInstance } from './userscripts';
import { initTranslator, updateTranslatorConfig } from './translator'; import { initTranslator, updateTranslatorConfig } from './translator';
import { setDeathAnimBlock, escapeHtml } from './utils'; import { setDeathAnimBlock, escapeHtml } from './utils';
import type { Keybind } from '../main/config'; import type { Keybind } from '../main/config';
@@ -10,7 +10,7 @@ import type { Keybind } from '../main/config';
// ── Save console methods before Krunker overwrites them ── // ── Save console methods before Krunker overwrites them ──
// Wrapped to forward errors/warnings always, and logs when verbose is enabled // Wrapped to forward errors/warnings always, and logs when verbose is enabled
let _verboseLogging = false; const _verboseLogging = false;
const _console = { const _console = {
log: (...args: unknown[]) => { log: (...args: unknown[]) => {
@@ -75,7 +75,7 @@ function updateRefreshNotification(): void {
if (refreshPopupEl) { refreshPopupEl.remove(); refreshPopupEl = null; } if (refreshPopupEl) { refreshPopupEl.remove(); refreshPopupEl = null; }
return; return;
} }
if (refreshPopupEl) { try { refreshPopupEl.remove(); } catch (_e) { /* noop */ } } if (refreshPopupEl) { try { refreshPopupEl.remove(); } catch { /* noop */ } }
refreshPopupEl = document.createElement('div'); refreshPopupEl = document.createElement('div');
refreshPopupEl.className = 'kpc-holder-update refresh-popup'; refreshPopupEl.className = 'kpc-holder-update refresh-popup';
if (refreshLevel === RefreshLevel.restart) { if (refreshLevel === RefreshLevel.restart) {
@@ -1115,7 +1115,7 @@ function renderUserscriptsSection(body: HTMLElement): void {
scriptRow.className = 'setting settName safety-0 bool'; scriptRow.className = 'setting settName safety-0 bool';
const displayName = escapeHtml(inst.meta.name || inst.filename); const displayName = escapeHtml(inst.meta.name || inst.filename);
let metaParts: string[] = []; const metaParts: string[] = [];
if (inst.meta.author) metaParts.push('by ' + escapeHtml(inst.meta.author)); if (inst.meta.author) metaParts.push('by ' + escapeHtml(inst.meta.author));
if (inst.meta.version) metaParts.push('v' + escapeHtml(inst.meta.version)); if (inst.meta.version) metaParts.push('v' + escapeHtml(inst.meta.version));
const metaLine = metaParts.length > 0 ? '<span class="kpc-us-meta">' + metaParts.join(' &middot; ') + '</span>' : ''; const metaLine = metaParts.length > 0 ? '<span class="kpc-us-meta">' + metaParts.join(' &middot; ') + '</span>' : '';
@@ -1352,7 +1352,7 @@ ipcRenderer.on('main_did-finish-load', () => {
Promise.all([ Promise.all([
ipcRenderer.invoke('get-all-config', ['ui', 'userscripts', 'game', 'translator', 'keybinds', 'discord']), ipcRenderer.invoke('get-all-config', ['ui', 'userscripts', 'game', 'translator', 'keybinds', 'discord']),
ipcRenderer.invoke('get-platform'), ipcRenderer.invoke('get-platform'),
]).then(([allConf, platformInfo]: [any, any]) => { ]).then(([allConf, _platformInfo]: [any, any]) => {
const uiConf = allConf.ui; const uiConf = allConf.ui;
const usConf = allConf.userscripts; const usConf = allConf.userscripts;
const gameConf = allConf.game; const gameConf = allConf.game;
@@ -1417,7 +1417,7 @@ ipcRenderer.on('main_did-finish-load', () => {
let gameStartTimestamp = Math.floor(Date.now() / 1000); let gameStartTimestamp = Math.floor(Date.now() / 1000);
function pollDiscordState(): void { function pollDiscordState(): void {
let details = ''; let details: string;
let state = ''; let state = '';
let startTimestamp: number | undefined = undefined; let startTimestamp: number | undefined = undefined;
@@ -1426,7 +1426,7 @@ ipcRenderer.on('main_did-finish-load', () => {
let gameActivity: any = null; let gameActivity: any = null;
if (typeof w.getGameActivity === 'function') { if (typeof w.getGameActivity === 'function') {
try { gameActivity = w.getGameActivity(); } catch {} try { gameActivity = w.getGameActivity(); } catch { /* game API unavailable */ }
} }
if (spectating) { if (spectating) {
+1
View File
@@ -138,6 +138,7 @@ const SYSTEM_PATTERNS = [
// ── Pre-translation filtering ── // ── Pre-translation filtering ──
function isLatinOnly(text: string): boolean { function isLatinOnly(text: string): boolean {
// eslint-disable-next-line no-control-regex
return /^[\x00-\x7F\u00C0-\u024F\u1E00-\u1EFF\s\d.,!?;:'"()\-/@#$%^&*+=~`[\]{}|\\<>]+$/u.test(text); return /^[\x00-\x7F\u00C0-\u024F\u1E00-\u1EFF\s\d.,!?;:'"()\-/@#$%^&*+=~`[\]{}|\\<>]+$/u.test(text);
} }
+3 -3
View File
@@ -108,7 +108,7 @@ function toggleCSS(css: string, identifier: string, value: boolean): void {
function executeScript( function executeScript(
instance: UserscriptInstance, instance: UserscriptInstance,
_console: { log: Function; warn: Function; error: Function }, _console: { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
): void { ): void {
if (instance.executed) return; if (instance.executed) return;
@@ -164,7 +164,7 @@ export function getInstances(): UserscriptInstance[] {
} }
export async function initUserscripts( export async function initUserscripts(
_console: { log: Function; warn: Function; error: Function }, _console: { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
): Promise<void> { ): Promise<void> {
const { scripts, tracker } = await ipcRenderer.invoke('userscripts-scan'); const { scripts, tracker } = await ipcRenderer.invoke('userscripts-scan');
if (!scripts || scripts.length === 0) { if (!scripts || scripts.length === 0) {
@@ -219,7 +219,7 @@ export async function initUserscripts(
export function setScriptEnabled( export function setScriptEnabled(
filename: string, filename: string,
enabled: boolean, enabled: boolean,
_console: { log: Function; warn: Function; error: Function }, _console: { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
): { needsReload: boolean } { ): { needsReload: boolean } {
const inst = instances.find(i => i.filename === filename); const inst = instances.find(i => i.filename === filename);
if (!inst) return { needsReload: false }; if (!inst) return { needsReload: false };