build: migrate eslint to v10 flat config and upgrade typescript

Replace .eslintrc.json with eslint.config.js using the new flat config
format required by ESLint 10. Swap @typescript-eslint/eslint-plugin and
@typescript-eslint/parser for the unified typescript-eslint package.
Upgrade TypeScript to 5.9 and @types/node to 25. Apply automatic code
fixes: remove unused catch bindings and preserve error cause in auth
header parsing.
This commit is contained in:
Zaid Humaimidi committed 2026-03-12 14:58:19 -07:00
1 parent 4a191afdec
commit 75908e624f
27 files changed
+1146 -1595

No files matched your search

-36
View File
@@ -1,36 +0,0 @@
{
"env": {
"es2022": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-shadow": ["error", { "allow": ["err"] }],
"handle-callback-err": "error",
"prefer-arrow-callback": "error",
"no-buffer-constructor": "error",
"prefer-const": ["error", { "destructuring": "all" }],
"eqeqeq": ["error", "always", { "null": "ignore" }],
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-use-before-define": [
"error",
{ "functions": false }
],
"@typescript-eslint/explicit-function-return-type": [
"error",
{ "allowExpressions": true }
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"no-prototype-builtins": 0
}
}
+1
View File
@@ -55,6 +55,7 @@ process.on("message", (message) => {
if (!script) { if (!script) {
const cwd = process.env["GENIEACS_EXT_DIR"]; const cwd = process.env["GENIEACS_EXT_DIR"];
process.chdir(cwd); process.chdir(cwd);
// eslint-disable-next-line @typescript-eslint/no-require-imports
script = require(`${cwd}/${fileName}`); script = require(`${cwd}/${fileName}`);
} }
+54
View File
@@ -0,0 +1,54 @@
import { defineConfig } from "@eslint/config-helpers";
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";
import globals from "globals";
export default defineConfig(
{
ignores: ["dist/", "test/*.js"],
},
eslint.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
{
languageOptions: {
globals: {
...globals.es2022,
...globals.node,
},
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"no-shadow": ["error", { allow: ["err"] }],
"handle-callback-err": "error",
"prefer-arrow-callback": "error",
"no-buffer-constructor": "error",
"prefer-const": ["error", { destructuring: "all" }],
eqeqeq: ["error", "always", { null: "ignore" }],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-use-before-define": [
"error",
{ functions: false },
],
"@typescript-eslint/explicit-function-return-type": [
"error",
{ allowExpressions: true },
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"no-prototype-builtins": "off",
},
},
{
files: ["ui/**/*.ts", "ui/**/*.tsx"],
languageOptions: {
globals: {
...globals.browser,
},
},
},
);
+2 -2
View File
@@ -103,7 +103,7 @@ export async function connectionRequest(
let remoteAddress; let remoteAddress;
try { try {
remoteAddress = new URL(connectionRequestUrl).hostname; remoteAddress = new URL(connectionRequestUrl).hostname;
} catch (err) { } catch {
return "Invalid connection request URL"; return "Invalid connection request URL";
} }
@@ -185,7 +185,7 @@ export async function connectionRequest(
() => true, () => true,
() => false, () => false,
); );
} catch (err) { } catch {
// Ignore invalid address // Ignore invalid address
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ function parseHeaderFeilds(str: string): Record<string, string> {
try { try {
value = JSON.parse(value); value = JSON.parse(value);
} catch (error) { } catch (error) {
throw new Error("Unable to parse auth header"); throw new Error("Unable to parse auth header", { cause: error });
} }
} }
res[name.trim()] = value; res[name.trim()] = value;
+1 -1
View File
@@ -157,7 +157,7 @@ export async function httpConnectionRequest(
authHeader = auth.parseWwwAuthenticateHeader( authHeader = auth.parseWwwAuthenticateHeader(
res.headers["www-authenticate"], res.headers["www-authenticate"],
); );
} catch (err) { } catch {
return "Connection request error: Error parsing www-authenticate header"; return "Connection request error: Error parsing www-authenticate header";
} }
[username, password, authExp] = await extractAuth(authExp, false); [username, password, authExp] = await extractAuth(authExp, false);
+4 -4
View File
@@ -89,7 +89,7 @@ async function authenticate(
authentication = auth.parseAuthorizationHeader( authentication = auth.parseAuthorizationHeader(
sessionContext.httpRequest.headers["authorization"], sessionContext.httpRequest.headers["authorization"],
); );
} catch (err) { } catch {
return false; return false;
} }
} }
@@ -1274,7 +1274,7 @@ async function clientError(
body: string, body: string,
msg: string, msg: string,
): Promise<void> { ): Promise<void> {
let debugEnabled = false; let debugEnabled: boolean;
let deviceId: string = null; let deviceId: string = null;
if (sessionContext) { if (sessionContext) {
@@ -1312,7 +1312,7 @@ async function clientError(
function decodeString(buffer: Buffer, charset: string): string { function decodeString(buffer: Buffer, charset: string): string {
try { try {
return buffer.toString(charset as BufferEncoding); return buffer.toString(charset as BufferEncoding);
} catch (err) { } catch {
if (encodingExists(charset)) return decode(buffer, charset); if (encodingExists(charset)) return decode(buffer, charset);
} }
return null; return null;
@@ -1383,7 +1383,7 @@ async function listenerAsync(
// connection is closed before the stream is finished. // connection is closed before the stream is finished.
// For Node 12.9+ we can just use stream.readableEnded // For Node 12.9+ we can just use stream.readableEnded
if (!readableEnded) throw new Error("Connection closed"); if (!readableEnded) throw new Error("Connection closed");
} catch (err) { } catch {
return; return;
} }
+1 -1
View File
@@ -78,7 +78,7 @@ export async function fetchDevice(
try { try {
res.push([Path.parse(path), t, attrs]); res.push([Path.parse(path), t, attrs]);
} catch (err) { } catch {
// The path parser is now more strict so we might be in a situation where // The path parser is now more strict so we might be in a situation where
// the database contains invalid paths from before this change So here we // the database contains invalid paths from before this change So here we
// encode the invalid characters. // encode the invalid characters.
+2 -2
View File
@@ -82,7 +82,7 @@ async function fetchPresets(): Promise<[string, Preset[]]> {
try { try {
schedule.duration = +parts.shift() * 1000; schedule.duration = +parts.shift() * 1000;
schedule.schedule = scheduling.parseCron(parts.join(" ")); schedule.schedule = scheduling.parseCron(parts.join(" "));
} catch (err) { } catch {
logger.warn({ logger.warn({
message: "Invalid preset schedule", message: "Invalid preset schedule",
preset: preset["_id"], preset: preset["_id"],
@@ -97,7 +97,7 @@ async function fetchPresets(): Promise<[string, Preset[]]> {
if (preset["precondition"]) { if (preset["precondition"]) {
try { try {
precondition = parse(preset["precondition"]); precondition = parse(preset["precondition"]);
} catch (error) { } catch {
precondition = convertOldPrecondition( precondition = convertOldPrecondition(
JSON.parse(preset["precondition"]), JSON.parse(preset["precondition"]),
); );
+2 -2
View File
@@ -20,12 +20,12 @@ const cidrs: [IPv4 | IPv6, number][] = [];
for (const str of FORWARDED_HEADER.split(",").map((s) => s.trim())) { for (const str of FORWARDED_HEADER.split(",").map((s) => s.trim())) {
try { try {
cidrs.push(parseCIDR(str)); cidrs.push(parseCIDR(str));
} catch (err) { } catch {
// Not a valid CIDR format, try parsing as IP // Not a valid CIDR format, try parsing as IP
try { try {
const ip = parse(str); const ip = parse(str);
cidrs.push([ip, ip.toByteArray().length * 8]); cidrs.push([ip, ip.toByteArray().length * 8]);
} catch (err) { } catch {
// Not a valid IP either, ignore // Not a valid IP either, ignore
} }
} }
+1 -1
View File
@@ -70,7 +70,7 @@ function matchEtag(etag: string, header: string): boolean {
if (t.startsWith("W/")) t = t.substring(2); if (t.startsWith("W/")) t = t.substring(2);
try { try {
t = JSON.parse(t); t = JSON.parse(t);
} catch (e) { } catch {
// Ignore // Ignore
} }
if (t === "*") return true; if (t === "*") return true;
+1 -1
View File
@@ -501,7 +501,7 @@ async function handler(
}; };
try { try {
await filesBucket.delete(filename as unknown as ObjectId); await filesBucket.delete(filename as unknown as ObjectId);
} catch (err) { } catch {
// Ignore error if file doesn't exist // Ignore error if file doesn't exist
} }
+1 -1
View File
@@ -93,7 +93,7 @@ export function start(
res.writeHead(500, { Connection: "close" }); res.writeHead(500, { Connection: "close" });
res.end(`${err.name}: ${err.message}`); res.end(`${err.name}: ${err.message}`);
} }
} catch (err) { } catch {
// Ignore // Ignore
} }
throw err; throw err;
+3 -3
View File
@@ -119,7 +119,7 @@ function parameterInfoList(xml: Element): [Path, boolean, boolean][] {
if (param && !param.endsWith(".")) if (param && !param.endsWith("."))
return [Path.parse(param), false, parsed]; return [Path.parse(param), false, parsed];
else return [Path.parse(param.slice(0, -1)), true, parsed]; else return [Path.parse(param.slice(0, -1)), true, parsed];
} catch (err) { } catch {
warnings.push({ warnings.push({
message: "Missing or invalid XML node", message: "Missing or invalid XML node",
element: "Name", element: "Name",
@@ -201,7 +201,7 @@ function parameterValueList(
} }
try { try {
return [Path.parse(param), parsed, valueType]; return [Path.parse(param), parsed, valueType];
} catch (err) { } catch {
warnings.push({ warnings.push({
message: "Missing or invalid XML node", message: "Missing or invalid XML node",
element: "Name", element: "Name",
@@ -250,7 +250,7 @@ function parameterAttributeList(xml: Element): [Path, number, string[]][] {
try { try {
return [Path.parse(param), notification, accessList]; return [Path.parse(param), notification, accessList];
} catch (err) { } catch {
warnings.push({ warnings.push({
message: "Missing or invalid XML node", message: "Missing or invalid XML node",
element: "Name", element: "Name",
+1 -1
View File
@@ -519,7 +519,7 @@ router.put("/files/:id", async (ctx) => {
try { try {
await db.deleteFile(id); await db.deleteFile(id);
} catch (err) { } catch {
// File doesn't exist, ignore // File doesn't exist, ignore
} }
+1 -1
View File
@@ -260,7 +260,7 @@ function flattenPreset(
try { try {
// Try parse to check expression validity // Try parse to check expression validity
parse(p.precondition as string); parse(p.precondition as string);
} catch (error) { } catch {
p.precondition = convertOldPrecondition( p.precondition = convertOldPrecondition(
JSON.parse(p.precondition as string), JSON.parse(p.precondition as string),
); );
+1 -1
View File
@@ -41,7 +41,7 @@ export function parseXmlDeclaration(buffer: Buffer): Attribute[] {
str = str.slice(0, str.indexOf("?>")); str = str.slice(0, str.indexOf("?>"));
try { try {
return parseAttrs(str.slice(5)); return parseAttrs(str.slice(5));
} catch (err) { } catch {
// Ignore // Ignore
} }
} }
+1050 -1517
View File
File diff suppressed because it is too large. Load diff
+6 -5
View File
@@ -48,26 +48,27 @@
"@codemirror/language": "^6.12.2", "@codemirror/language": "^6.12.2",
"@codemirror/state": "^6.5.4", "@codemirror/state": "^6.5.4",
"@codemirror/view": "^6.39.16", "@codemirror/view": "^6.39.16",
"@eslint/js": "^10.0.1",
"@tailwindcss/cli": "^4.2.1", "@tailwindcss/cli": "^4.2.1",
"@tailwindcss/forms": "^0.5.11", "@tailwindcss/forms": "^0.5.11",
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/koa": "^2.15.0", "@types/koa": "^2.15.0",
"@types/koa-compress": "^4.0.7", "@types/koa-compress": "^4.0.7",
"@types/mithril": "^2.2.7", "@types/mithril": "^2.2.7",
"@types/node": "^20.19.37", "@types/node": "^25.3.5",
"@types/parsimmon": "^1.10.9", "@types/parsimmon": "^1.10.9",
"@types/seedrandom": "^3.0.8", "@types/seedrandom": "^3.0.8",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"esbuild": "^0.27.4", "esbuild": "^0.27.4",
"eslint": "^8.57.1", "eslint": "^10.0.3",
"eslint-config-prettier": "^9.1.2", "eslint-config-prettier": "^10.1.8",
"globals": "^17.4.0",
"mithril": "^2.3.8", "mithril": "^2.3.8",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"sql.js": "^1.14.1", "sql.js": "^1.14.1",
"svgo": "^3.3.3", "svgo": "^3.3.3",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"yaml": "^1.10.2" "yaml": "^1.10.2"
}, },
"engines": { "engines": {
-6
View File
@@ -1,6 +0,0 @@
{
"env": {
"browser": true,
"node": false
}
}
+3 -1
View File
@@ -119,7 +119,9 @@ export default class Autocomplete {
if (!this.visible) { if (!this.visible) {
if (!this.hideTimeout) { if (!this.hideTimeout) {
document.body.appendChild(this.container); document.body.appendChild(this.container);
window.getComputedStyle(this.container).opacity; // Force style recalc so the initial opacity is resolved before
// setting it to "1", allowing the CSS transition to play.
void window.getComputedStyle(this.container).opacity;
} else { } else {
clearTimeout(this.hideTimeout); clearTimeout(this.hideTimeout);
this.hideTimeout = null; this.hideTimeout = null;
+1 -1
View File
@@ -47,7 +47,7 @@ const component: ClosureComponent = (vn): Component => {
try { try {
const url = new URL(param.value[0]); const url = new URL(param.value[0]);
h = url.hostname; h = url.hostname;
} catch (err) { } catch {
// Ignore // Ignore
} }
+1 -1
View File
@@ -31,7 +31,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
try { try {
object.value = stringify(parse(object.value || "")); object.value = stringify(parse(object.value || ""));
} catch (err) { } catch {
return void resolve({ return void resolve({
value: "Config value must be valid expression", value: "Config value must be valid expression",
}); });
+1 -1
View File
@@ -83,7 +83,7 @@ const component: ClosureComponent<Attrs> = (initialVnode) => {
const list = filterList.map((f, idx) => { const list = filterList.map((f, idx) => {
try { try {
return parseFilter(attrs.resource, f); return parseFilter(attrs.resource, f);
} catch (err) { } catch {
filterInvalid |= 1 << idx; filterInvalid |= 1 << idx;
} }
return null; return null;
+2 -2
View File
@@ -90,7 +90,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
if (object.filter) { if (object.filter) {
try { try {
object.filter = stringify(memoizedParse(object.filter)); object.filter = stringify(memoizedParse(object.filter));
} catch (err) { } catch {
return void resolve({ return void resolve({
filter: "Filter must be valid expression", filter: "Filter must be valid expression",
}); });
@@ -100,7 +100,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
if (object.validate) { if (object.validate) {
try { try {
object.validate = stringify(memoizedParse(object.validate)); object.validate = stringify(memoizedParse(object.validate));
} catch (err) { } catch {
return void resolve({ return void resolve({
validate: "Validate must be valid expression", validate: "Validate must be valid expression",
}); });
+1 -1
View File
@@ -72,7 +72,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
if (object.precondition) { if (object.precondition) {
try { try {
object.precondition = stringify(memoizedParse(object.precondition)); object.precondition = stringify(memoizedParse(object.precondition));
} catch (err) { } catch {
return void resolve({ return void resolve({
precondition: "Precondition must be valid expression", precondition: "Precondition must be valid expression",
}); });
+4 -2
View File
@@ -113,7 +113,7 @@ function checkConnection(): void {
setTimestamp(now2); setTimestamp(now2);
m.redraw(); m.redraw();
} }
} catch (err) { } catch {
// Ignore in case of missing or invalid Date header // Ignore in case of missing or invalid Date header
} }
@@ -200,7 +200,9 @@ export async function xhrRequest(
try { try {
response = xhr.responseText ? JSON.parse(xhr.responseText) : null; response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
} catch (err) { } catch (err) {
throw new Error("Invalid JSON: " + xhr.responseText.slice(0, 80)); throw new Error("Invalid JSON: " + xhr.responseText.slice(0, 80), {
cause: err,
});
} }
} else { } else {
response = xhr.responseText; response = xhr.responseText;