refactor(main): audit warning suppression rules

This commit is contained in:
ZacharyZhang-NY committed 2026-04-22 19:16:22 -04:00
1 parent 0235bc9b0d
commit e43a89096d
5 files changed
+116 -59

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
import "./suppress-warnings";
import "./install-main-warning-suppressions";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { app } from "electron";
@@ -0,0 +1,18 @@
import {
classifyConsoleErrorSuppression,
classifyWarningSuppression,
} from "./main-warning-suppression-rules";
const originalEmitWarning = process.emitWarning.bind(process);
process.emitWarning = ((warning: unknown, ...rest: unknown[]) => {
if (classifyWarningSuppression(warning)) return;
return (originalEmitWarning as (...args: unknown[]) => void)(warning, ...rest);
}) as typeof process.emitWarning;
const originalConsoleError = console.error.bind(console);
console.error = ((...args: unknown[]) => {
if (classifyConsoleErrorSuppression(args)) return;
return originalConsoleError(...args);
}) as typeof console.error;
@@ -0,0 +1,53 @@
export type WarningSuppressionCategory =
| "node-sqlite-experimental-warning"
| "electron-disposed-web-frame-send";
interface SuppressionRule<TInput> {
category: WarningSuppressionCategory;
reason: string;
matches: (input: TInput) => boolean;
}
export const WARNING_SUPPRESSION_RULES: ReadonlyArray<SuppressionRule<unknown>> = [
{
category: "node-sqlite-experimental-warning",
reason: "Node emits this before the local SQLite profile store can run; the runtime version pins the API surface.",
matches: isSqliteExperimentalWarning,
},
];
export const CONSOLE_ERROR_SUPPRESSION_RULES: ReadonlyArray<SuppressionRule<unknown[]>> = [
{
category: "electron-disposed-web-frame-send",
reason: "Electron logs a send to a disposed renderer frame during close, reload, or HMR after the send has become a no-op.",
matches: isDisposedWebFrameSend,
},
];
export function classifyWarningSuppression(warning: unknown): WarningSuppressionCategory | null {
return WARNING_SUPPRESSION_RULES.find((rule) => rule.matches(warning))?.category ?? null;
}
export function classifyConsoleErrorSuppression(args: unknown[]): WarningSuppressionCategory | null {
return CONSOLE_ERROR_SUPPRESSION_RULES.find((rule) => rule.matches(args))?.category ?? null;
}
function isSqliteExperimentalWarning(warning: unknown): boolean {
if (typeof warning === "string") {
return /SQLite is an experimental feature/.test(warning);
}
if (warning instanceof Error) {
return warning.name === "ExperimentalWarning" && /SQLite/.test(warning.message);
}
return false;
}
function isDisposedWebFrameSend(args: unknown[]): boolean {
if (args.length === 0) return false;
const first = args[0];
if (typeof first !== "string") return false;
if (!first.startsWith("Error sending from webFrameMain")) return false;
const payload = args[1];
const message = payload instanceof Error ? payload.message : String(payload ?? "");
return message.includes("Render frame was disposed");
}
-58
View File
@@ -1,58 +0,0 @@
/**
* Silences two Electron/Node sources of stderr spam that are pure
* lifecycle noise, not logic bugs:
*
* 1. `node:sqlite` ExperimentalWarning on first import — emitted by the
* Node runtime itself. Must be installed before any module touches
* `node:sqlite`, or the warning has already fired.
*
* 2. `Error sending from webFrameMain:` — emitted by Electron's own
* `WebFrameMain.prototype.send` when the renderer frame was disposed
* mid-send (window close, dev HMR, reload). Electron catches the
* native exception internally and `console.error`s it; the send is
* already a no-op by the time our `webContents.send` returns, and
* nothing downstream reads the message. With every agent-event fan
* out producing two sends, a single dead renderer floods the log
* with thousands of copies and can stall the main event loop on
* synchronous stderr I/O. We filter only this specific prefix so
* real console errors still surface.
*
* Targeted: everything else passes through unchanged.
*/
const originalEmitWarning = process.emitWarning.bind(process);
function isSqliteExperimentalWarning(warning: unknown): boolean {
if (typeof warning === "string") {
return /SQLite is an experimental feature/.test(warning);
}
if (warning instanceof Error) {
return warning.name === "ExperimentalWarning" && /SQLite/.test(warning.message);
}
return false;
}
process.emitWarning = ((warning: unknown, ...rest: unknown[]) => {
if (isSqliteExperimentalWarning(warning)) return;
return (originalEmitWarning as (...args: unknown[]) => void)(warning, ...rest);
}) as typeof process.emitWarning;
const originalConsoleError = console.error.bind(console);
function isDisposedWebFrameNoise(args: unknown[]): boolean {
if (args.length === 0) return false;
const first = args[0];
if (typeof first !== "string") return false;
if (!first.startsWith("Error sending from webFrameMain")) return false;
// Only swallow when the accompanying error is the disposal signal;
// any other payload surfaces normally so genuine IPC failures still
// show up in logs.
const payload = args[1];
const message = payload instanceof Error ? payload.message : String(payload ?? "");
return message.includes("Render frame was disposed");
}
console.error = ((...args: unknown[]) => {
if (isDisposedWebFrameNoise(args)) return;
return originalConsoleError(...args);
}) as typeof console.error;
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import {
CONSOLE_ERROR_SUPPRESSION_RULES,
WARNING_SUPPRESSION_RULES,
classifyConsoleErrorSuppression,
classifyWarningSuppression,
} from "../../src/main/main-warning-suppression-rules";
describe("main warning suppression rules", () => {
it("documents every warning suppression category with an operator reason", () => {
for (const rule of [...WARNING_SUPPRESSION_RULES, ...CONSOLE_ERROR_SUPPRESSION_RULES]) {
expect(rule.category).toMatch(/^[a-z0-9-]+$/);
expect(rule.reason.length).toBeGreaterThan(40);
}
});
it("suppresses only the node sqlite experimental warning", () => {
const sqliteWarning = new Error("SQLite is an experimental feature and might change at any time");
sqliteWarning.name = "ExperimentalWarning";
const genericWarning = new Error("fetch is an experimental feature");
genericWarning.name = "ExperimentalWarning";
expect(classifyWarningSuppression(sqliteWarning)).toBe("node-sqlite-experimental-warning");
expect(classifyWarningSuppression("SQLite is an experimental feature and might change at any time"))
.toBe("node-sqlite-experimental-warning");
expect(classifyWarningSuppression(genericWarning)).toBeNull();
expect(classifyWarningSuppression("deprecation warning")).toBeNull();
});
it("suppresses only disposed renderer frame send noise", () => {
expect(classifyConsoleErrorSuppression([
"Error sending from webFrameMain:",
new Error("Render frame was disposed before WebFrameMain could send"),
])).toBe("electron-disposed-web-frame-send");
expect(classifyConsoleErrorSuppression([
"Error sending from webFrameMain:",
new Error("Permission denied"),
])).toBeNull();
expect(classifyConsoleErrorSuppression(["Unhandled renderer error", new Error("Render frame was disposed")]))
.toBeNull();
});
});