refactor(main): audit warning suppression rules
This commit is contained in:
1 parent
0235bc9b0d
commit
e43a89096d
5 files changed
+116
-59
No files matched your search
+1
-1
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user