diff --git a/CHANGELOG.md b/CHANGELOG.md index 1779b0b3..9194236b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Added `xcodebuildmcp purge` to report and explicitly clean XcodeBuildMCP-managed workspace storage, including opt-in DerivedData cleanup with dry-run and confirmation safeguards. - Added `extraArgs` as a first-class session-default value. Repo config or runtime defaults can now carry common `xcodebuild` flags (for example `-skipPackagePluginValidation` or `-disableAutomaticPackageResolution`) so they don't need repeating on every build or test call. Per-call `extraArgs` replace matching configured flags or build settings and append after non-matching defaults, while an explicit empty array (`extraArgs: []`) clears the defaults for a single call. The session management tools show, set, sync, and clear `extraArgs` alongside the other defaults. +### Fixed + +- Fixed `suppressWarnings` being ignored in settled build, build-run, and test output. The flag was honored only while streaming, so warnings still reached the final MCP tool response ([#447](https://github.com/getsentry/XcodeBuildMCP/issues/447)). + ## [2.6.2] ### Fixed diff --git a/src/utils/renderers/__tests__/cli-text-renderer.test.ts b/src/utils/renderers/__tests__/cli-text-renderer.test.ts index cc8789a4..b2400d4b 100644 --- a/src/utils/renderers/__tests__/cli-text-renderer.test.ts +++ b/src/utils/renderers/__tests__/cli-text-renderer.test.ts @@ -175,6 +175,23 @@ describe('cli-text-renderer', () => { ); }); + it('omits settled build warnings when suppressWarnings is set', () => { + const structuredOutput = buildOutput({ + diagnostics: { + warnings: [{ message: 'unused variable "foo"', location: 'App.swift:12' }], + errors: [], + }, + }); + + const shown = renderCliTextTranscript({ structuredOutput }); + expect(shown).toContain('Warnings (1):'); + expect(shown).toContain('unused variable'); + + const suppressed = renderCliTextTranscript({ structuredOutput, suppressWarnings: true }); + expect(suppressed).not.toContain('Warnings (1):'); + expect(suppressed).not.toContain('unused variable'); + }); + it('renders non-interactive test progress durably and deduplicates repeated counts', () => { const output = renderCliTextTranscript({ items: [ diff --git a/src/utils/renderers/__tests__/domain-result-text.test.ts b/src/utils/renderers/__tests__/domain-result-text.test.ts index db1d314c..ca9f7e49 100644 --- a/src/utils/renderers/__tests__/domain-result-text.test.ts +++ b/src/utils/renderers/__tests__/domain-result-text.test.ts @@ -1,10 +1,29 @@ import { describe, expect, it } from 'vitest'; import type { + BuildResultDomainResult, LaunchResultDomainResult, UiActionResultDomainResult, } from '../../../types/domain-results.ts'; import { renderDomainResultTextItems } from '../domain-result-text.ts'; +function buildResultWithDiagnostics(): BuildResultDomainResult { + return { + kind: 'build-result', + didError: false, + error: null, + summary: { status: 'SUCCEEDED' }, + artifacts: { scheme: 'MyScheme' }, + diagnostics: { + warnings: [{ message: 'unused variable "foo"', location: 'App.swift:12' }], + errors: [{ message: 'cannot find "bar" in scope', location: 'App.swift:20' }], + }, + }; +} + +function sectionTitles(items: ReturnType): string[] { + return items.flatMap((item) => (item.type === 'section' ? [item.title] : [])); +} + function uiActionResult(action: UiActionResultDomainResult['action']): UiActionResultDomainResult { return { kind: 'ui-action-result', @@ -114,6 +133,24 @@ describe('renderDomainResultTextItems', () => { `); }); + it('renders build warnings by default', () => { + const titles = sectionTitles(renderDomainResultTextItems(buildResultWithDiagnostics())); + + expect(titles).toContain('Warnings (1):'); + expect(titles).toContain('Errors (1):'); + }); + + it('omits build warnings when suppressWarnings is set, keeping errors', () => { + const titles = sectionTitles( + renderDomainResultTextItems(buildResultWithDiagnostics(), undefined, { + suppressWarnings: true, + }), + ); + + expect(titles).not.toContain('Warnings (1):'); + expect(titles).toContain('Errors (1):'); + }); + it('renders batch UI action results', () => { expect( renderDomainResultTextItems( diff --git a/src/utils/renderers/cli-text-renderer.ts b/src/utils/renderers/cli-text-renderer.ts index 96e0732c..fa37dae1 100644 --- a/src/utils/renderers/cli-text-renderer.ts +++ b/src/utils/renderers/cli-text-renderer.ts @@ -444,6 +444,7 @@ function createCliTextProcessor(options: CliTextProcessorOptions): TranscriptRen const structuredItems = renderDomainResultTextItems( structuredOutput.result, structuredOutput.renderHints, + { suppressWarnings }, ); const replayItems = sawIncomingHeaderEvent && structuredItems[0]?.type === 'header' @@ -456,6 +457,7 @@ function createCliTextProcessor(options: CliTextProcessorOptions): TranscriptRen const structuredItems = renderDomainResultTextItems( structuredOutput.result, structuredOutput.renderHints, + { suppressWarnings }, ); const replayItems = structuredItems.filter((item) => { if (sawIncomingHeaderEvent && item.type === 'header') return false; diff --git a/src/utils/renderers/domain-result-text.ts b/src/utils/renderers/domain-result-text.ts index a117546e..dfc8f281 100644 --- a/src/utils/renderers/domain-result-text.ts +++ b/src/utils/renderers/domain-result-text.ts @@ -87,6 +87,10 @@ export type TextRendererBlock = export type TextRenderableItem = RenderItem | TextRendererBlock; +export interface DomainResultTextOptions { + suppressWarnings?: boolean; +} + const SESSION_DEFAULT_KEYS = [ 'projectPath', 'workspacePath', @@ -248,6 +252,7 @@ function createMarkedDiagnosticLines( function createStandardDiagnosticSections( diagnostics: BasicDiagnostics | TestDiagnostics | undefined, + suppressWarnings = false, ): SectionTextBlock[] { const sections: SectionTextBlock[] = []; if (!diagnostics) { @@ -265,7 +270,7 @@ function createStandardDiagnosticSections( ), ); } - if (diagnostics.warnings.length > 0) { + if (!suppressWarnings && diagnostics.warnings.length > 0) { sections.push( createSection( `Warnings (${diagnostics.warnings.length}):`, @@ -2013,8 +2018,11 @@ function createCleanResultItems( return items; } -function createDiagnosticSections(result: ToolDomainResult): SectionTextBlock[] { - return createStandardDiagnosticSections(getResultDiagnostics(result)); +function createDiagnosticSections( + result: ToolDomainResult, + suppressWarnings: boolean, +): SectionTextBlock[] { + return createStandardDiagnosticSections(getResultDiagnostics(result), suppressWarnings); } function createSummaryBlock(result: ToolDomainResult): SummaryTextBlock | null { @@ -2072,8 +2080,12 @@ function createTestDiscoveryProgress( function createBuildLikeDiagnosticItems( result: Extract, + suppressWarnings: boolean, ): TextRenderableItem[] { - return createStandardDiagnosticSections('diagnostics' in result ? result.diagnostics : undefined); + return createStandardDiagnosticSections( + 'diagnostics' in result ? result.diagnostics : undefined, + suppressWarnings, + ); } function createBuildRunSyntheticStepItems( @@ -2576,12 +2588,14 @@ export function createNextStepsBlock( export function renderDomainResultTextItems( result: ToolDomainResult, hints?: RenderHints, + options?: DomainResultTextOptions, ): TextRenderableItem[] { const specialCaseItems = createSpecialCaseItems(result, hints); if (specialCaseItems) { return specialCaseItems; } + const suppressWarnings = options?.suppressWarnings ?? false; const items: TextRenderableItem[] = []; if ( result.kind === 'build-result' || @@ -2599,7 +2613,7 @@ export function renderDomainResultTextItems( } } const tailItems = createBuildLikeTailItems(result); - items.push(...createBuildLikeDiagnosticItems(result)); + items.push(...createBuildLikeDiagnosticItems(result, suppressWarnings)); if (result.kind === 'build-run-result') { items.push(...createBuildRunSyntheticStepItems(result)); } @@ -2623,7 +2637,7 @@ export function renderDomainResultTextItems( return items; } - items.push(...createDiagnosticSections(result)); + items.push(...createDiagnosticSections(result, suppressWarnings)); const summary = createSummaryBlock(result); if (summary) { items.push(summary);