diff --git a/docs/plans/state.json b/docs/plans/state.json index 226b949..4047f12 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -97,6 +97,19 @@ "test_status": "pnpm test:run src/cli/openai-key.test.ts src/cli/anthropic-auth.test.ts src/cli/openai-auth.test.ts src/cli/zai-auth.test.ts + pnpm typecheck passing" }, + "cli-suppress-punycode-deprecation-warning": { + "status": "completed", + "date": "2026-02-16", + "updated": "2026-02-16", + "summary": "Added a CLI bootstrap warning filter to suppress Node DEP0040 (`punycode` deprecation) noise from transitive dependencies while preserving all other warnings. Loaded filter at CLI startup and added matcher tests.", + "files_modified": [ + "src/cli/index.ts", + "src/cli/suppressNodeWarnings.ts", + "src/cli/suppressNodeWarnings.test.ts" + ], + "test_status": "pnpm test:run src/cli/suppressNodeWarnings.test.ts src/cli/index.test.ts + pnpm typecheck + pnpm build passing" + }, + "deployment-port-env-override": { "status": "completed", "date": "2026-02-16", diff --git a/src/cli/index.ts b/src/cli/index.ts index 727c4b0..97920c0 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -4,6 +4,7 @@ // Silent no-op if the file doesn't exist. try { process.loadEnvFile(); } catch { /* .env not found — that's fine */ } +import './suppressNodeWarnings.js'; import { Command } from 'commander'; import { registerStartCommand } from './start.js'; import { registerSendCommand } from './send.js'; diff --git a/src/cli/suppressNodeWarnings.test.ts b/src/cli/suppressNodeWarnings.test.ts new file mode 100644 index 0000000..49aaa11 --- /dev/null +++ b/src/cli/suppressNodeWarnings.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { shouldSuppressNodeWarning } from './suppressNodeWarnings.js'; + +describe('shouldSuppressNodeWarning', () => { + it('suppresses DEP0040 warning code', () => { + expect(shouldSuppressNodeWarning('DEP0040', 'any message')).toBe(true); + }); + + it('suppresses punycode deprecation by message text', () => { + expect(shouldSuppressNodeWarning(undefined, 'The `punycode` module is deprecated.')).toBe(true); + }); + + it('does not suppress unrelated warnings', () => { + expect(shouldSuppressNodeWarning('DEP0001', 'different deprecation')).toBe(false); + }); +}); diff --git a/src/cli/suppressNodeWarnings.ts b/src/cli/suppressNodeWarnings.ts new file mode 100644 index 0000000..5f5e094 --- /dev/null +++ b/src/cli/suppressNodeWarnings.ts @@ -0,0 +1,47 @@ +/** + * Suppress known noisy runtime deprecations from transitive dependencies while + * keeping all other warnings visible. + */ +export function shouldSuppressNodeWarning(code: string | undefined, message: string): boolean { + if (code === 'DEP0040') { + return true; + } + return /`punycode` module is deprecated/i.test(message); +} + +export function installNodeWarningFilter(): void { + const stateKey = Symbol.for('flynn.nodeWarningFilterInstalled'); + const globalState = globalThis as Record; + if (globalState[stateKey]) { + return; + } + globalState[stateKey] = true; + + const originalEmitWarning = process.emitWarning.bind(process); + + process.emitWarning = ((warning: unknown, ...args: unknown[]) => { + const message = warning instanceof Error + ? warning.message + : typeof warning === 'string' + ? warning + : String(warning); + + let code: string | undefined; + if (warning instanceof Error && typeof (warning as { code?: unknown }).code === 'string') { + code = (warning as { code?: string }).code; + } + if (typeof args[0] === 'string') { + code = args[0]; + } else if (args[0] && typeof args[0] === 'object' && typeof (args[0] as { code?: unknown }).code === 'string') { + code = (args[0] as { code?: string }).code; + } + + if (shouldSuppressNodeWarning(code, message)) { + return; + } + + (originalEmitWarning as (...params: unknown[]) => void)(warning, ...args); + }) as typeof process.emitWarning; +} + +installNodeWarningFilter();