refactor(debug): replace ConsolePatcher with debugLogger and update error reporting

- Replace ConsolePatcher with centralized debugLogger utility
- Refactor errorReporting to use debugLogger instead of file-based reporting
- Remove user-facing console message components:
  - Delete ConsolePatcher.ts, useConsoleMessages.ts/hook
  - Delete ConsoleSummaryDisplay.tsx, DetailedMessagesDisplay.tsx
- Update all tests in packages/core and packages/cli:
  - Mock debugLogger where needed
  - Remove assertions for console output on non-critical errors
  - Keep debugLogger assertions for fatal/network errors
  - Use HOME directory mocking for hermetic file system tests

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-02-02 17:37:54 +08:00
parent 135df54f27
commit 89e3c2cd7a
64 changed files with 1240 additions and 2416 deletions

View file

@ -20,6 +20,15 @@ import type { Settings } from './settings.js';
import * as ServerConfig from '@qwen-code/qwen-code-core';
import { isWorkspaceTrusted } from './trustedFolders.js';
const mockWriteStderrLine = vi.hoisted(() => vi.fn());
const mockWriteStdoutLine = vi.hoisted(() => vi.fn());
vi.mock('../utils/stdioHelpers.js', () => ({
writeStderrLine: mockWriteStderrLine,
writeStdoutLine: mockWriteStdoutLine,
clearScreen: vi.fn(),
}));
const createNativeLspServiceInstance = () => ({
discoverAndPrepare: vi.fn(),
start: vi.fn(),
@ -158,23 +167,19 @@ describe('parseArguments', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining(
'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',
),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should throw an error when using short flags -p and -i together', async () => {
@ -190,23 +195,19 @@ describe('parseArguments', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining(
'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',
),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should allow --prompt without --prompt-interactive', async () => {
@ -389,23 +390,19 @@ describe('parseArguments', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining(
'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.',
),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should throw an error when using short flags -y and --approval-mode together', async () => {
@ -414,23 +411,19 @@ describe('parseArguments', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining(
'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.',
),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should throw an error when include-partial-messages is used without stream-json output', async () => {
@ -439,23 +432,19 @@ describe('parseArguments', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining(
'--include-partial-messages requires --output-format stream-json',
),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should parse stream-json formats and include-partial-messages flag', async () => {
@ -496,21 +485,17 @@ describe('parseArguments', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining('Invalid values:'),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should support comma-separated values for --allowed-tools', async () => {
@ -900,21 +885,17 @@ describe('loadCliConfig telemetry', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining('Invalid values:'),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
});
@ -2138,17 +2119,14 @@ describe('Output format', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining('Invalid values:'),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
});
@ -2172,23 +2150,19 @@ describe('parseArguments with positional prompt', () => {
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
const mockConsoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
mockWriteStderrLine.mockClear();
await expect(parseArguments({} as Settings)).rejects.toThrow(
'process.exit called',
);
expect(mockConsoleError).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
expect.stringContaining(
'Cannot use both a positional prompt and the --prompt (-p) flag together',
),
);
mockExit.mockRestore();
mockConsoleError.mockRestore();
});
it('should correctly parse a positional prompt to query field', async () => {

View file

@ -45,7 +45,6 @@ export enum Command {
PASTE_CLIPBOARD_IMAGE = 'pasteClipboardImage',
// App level bindings
SHOW_ERROR_DETAILS = 'showErrorDetails',
TOGGLE_TOOL_DESCRIPTIONS = 'toggleToolDescriptions',
TOGGLE_IDE_CONTEXT_DETAIL = 'toggleIDEContextDetail',
QUIT = 'quit',
@ -156,7 +155,6 @@ export const defaultKeyBindings: KeyBindingConfig = {
[Command.PASTE_CLIPBOARD_IMAGE]: [{ key: 'v', ctrl: true }],
// App level bindings
[Command.SHOW_ERROR_DETAILS]: [{ key: 'o', ctrl: true }],
[Command.TOGGLE_TOOL_DESCRIPTIONS]: [{ key: 't', ctrl: true }],
[Command.TOGGLE_IDE_CONTEXT_DETAIL]: [{ key: 'g', ctrl: true }],
[Command.QUIT]: [{ key: 'c', ctrl: true }],