/
githubmirror
/
angular-cli
Обзор
Документация
Войти
/
githubmirror
/
angular-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/angular/build/src/builders/karma/progress-reporter.ts
118 строк
4 KB
Charles Lyding
fix(@angular/build): safeguard Karma builder stream controller against closed state
28 июл 2026, 11:01
28 июл 2026, 11:01
5914877
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import type { BuilderOutput } from '@angular-devkit/architect'; import type { Config, ConfigOptions } from 'karma'; import type { ReadableStreamController } from 'node:stream/web'; import { writeTestFiles } from '../../utils/test-files'; import type { ApplicationBuilderInternalOptions } from '../application/options'; import type { Result } from '../application/results'; import { ResultKind } from '../application/results'; import type { LatestBuildFiles } from './assets-middleware'; const LATEST_BUILD_FILES_TOKEN = 'angularLatestBuildFiles'; interface BuildOptions extends ApplicationBuilderInternalOptions { // We know that it's always a string since we set it. outputPath: string; } export function injectKarmaReporter( buildOptions: BuildOptions, buildIterator: AsyncIterator<Result>, karmaConfig: Config & ConfigOptions, controller: ReadableStreamController<BuilderOutput>, ): void { const reporterName = 'angular-progress-notifier'; interface RunCompleteInfo { exitCode: number; } interface KarmaEmitter { refreshFiles(): void; } class ProgressNotifierReporter { static $inject = ['emitter', LATEST_BUILD_FILES_TOKEN]; // Needed for the karma reporter interface, see https://github.com/angular/angular-cli/issues/31629 adapters = []; constructor( private readonly emitter: KarmaEmitter, private readonly latestBuildFiles: LatestBuildFiles, ) { this.startWatchingBuild(); } private startWatchingBuild() { void (async () => { // This is effectively "for await of but skip what's already consumed". while (true) { const { done, value: buildOutput } = await buildIterator.next(); if (done) { break; } if (controller.desiredSize === null) { break; } if (buildOutput.kind === ResultKind.Failure) { 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 ) { if (buildOutput.kind === ResultKind.Full) { this.latestBuildFiles.files = buildOutput.files; } else { this.latestBuildFiles.files = { ...this.latestBuildFiles.files, ...buildOutput.files, }; } await writeTestFiles(buildOutput.files, buildOptions.outputPath); this.emitter.refreshFiles(); } } })(); } onRunComplete = function (_browsers: unknown, results: RunCompleteInfo): void { if (controller.desiredSize !== null) { try { controller.enqueue({ success: results.exitCode === 0 }); } catch { // Stream controller may already be closed or cancelled } } }; } karmaConfig.reporters ??= []; karmaConfig.reporters.push(reporterName); karmaConfig.plugins ??= []; karmaConfig.plugins.push({ [`reporter:${reporterName}`]: [ 'factory', Object.assign( (...args: ConstructorParameters<typeof ProgressNotifierReporter>) => new ProgressNotifierReporter(...args), ProgressNotifierReporter, ), ], }); }