diff --git a/packages/angular/build/src/builders/karma/application_builder.ts b/packages/angular/build/src/builders/karma/application_builder.ts index b9a28e1bf0b0..d0adb6c3cf0e 100644 --- a/packages/angular/build/src/builders/karma/application_builder.ts +++ b/packages/angular/build/src/builders/karma/application_builder.ts @@ -11,7 +11,6 @@ import type { Config, ConfigOptions, FilePattern, InlinePluginDef, Server } from import { randomUUID } from 'node:crypto'; import { rmSync } from 'node:fs'; import * as fs from 'node:fs/promises'; -import { createRequire } from 'node:module'; import path from 'node:path'; import { ReadableStream } from 'node:stream/web'; import { createVirtualModulePlugin } from '../../tools/esbuild/virtual-module-plugin'; @@ -66,8 +65,14 @@ export function execute( init = await initializeApplication(normalizedOptions, context, karmaOptions, transforms); } catch (err) { if (err instanceof ApplicationBuildError) { - controller.enqueue({ success: false, message: err.message }); - controller.close(); + if (controller.desiredSize !== null) { + try { + controller.enqueue({ success: false, message: err.message }); + controller.close(); + } catch { + // Stream controller may already be closed or cancelled + } + } return; } @@ -85,8 +90,14 @@ export function execute( // Close the stream once the Karma server returns. karmaServer = new karma.Server(karmaConfig as Config, (exitCode) => { - controller.enqueue({ success: exitCode === 0 }); - controller.close(); + if (controller.desiredSize !== null) { + try { + controller.enqueue({ success: exitCode === 0 }); + controller.close(); + } catch { + // Stream controller may already be closed or cancelled + } + } }); await karmaServer.start(); diff --git a/packages/angular/build/src/builders/karma/progress-reporter.ts b/packages/angular/build/src/builders/karma/progress-reporter.ts index 16824badd095..6fc92b6c74c8 100644 --- a/packages/angular/build/src/builders/karma/progress-reporter.ts +++ b/packages/angular/build/src/builders/karma/progress-reporter.ts @@ -59,8 +59,18 @@ export function injectKarmaReporter( break; } + if (controller.desiredSize === null) { + break; + } + if (buildOutput.kind === ResultKind.Failure) { - controller.enqueue({ success: false, message: 'Build failed' }); + if (controller.desiredSize !== null) { + try { + controller.enqueue({ success: false, message: 'Build failed' }); + } catch { + // Stream controller may already be closed or cancelled + } + } } else if ( buildOutput.kind === ResultKind.Incremental || buildOutput.kind === ResultKind.Full @@ -81,10 +91,12 @@ export function injectKarmaReporter( } onRunComplete = function (_browsers: unknown, results: RunCompleteInfo): void { - if (results.exitCode === 0) { - controller.enqueue({ success: true }); - } else { - controller.enqueue({ success: false }); + if (controller.desiredSize !== null) { + try { + controller.enqueue({ success: results.exitCode === 0 }); + } catch { + // Stream controller may already be closed or cancelled + } } }; } diff --git a/packages/angular/build/src/builders/karma/tests/behavior/errors_spec.ts b/packages/angular/build/src/builders/karma/tests/behavior/errors_spec.ts index 366cc2aa20a1..a69698b8d743 100644 --- a/packages/angular/build/src/builders/karma/tests/behavior/errors_spec.ts +++ b/packages/angular/build/src/builders/karma/tests/behavior/errors_spec.ts @@ -28,5 +28,18 @@ describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { expect(result?.success).toBeFalse(); }); + + it('handles stream cancellation gracefully in watch mode', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + watch: true, + }); + + const { result } = await harness.executeOnce({ + outputLogsOnFailure: false, + }); + + expect(result?.success).toBeTrue(); + }); }); });