66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { BuildOutputFileType } from '@angular/build' ;
109import {
1110 ApplicationBuilderInternalOptions ,
1211 Result ,
@@ -15,20 +14,22 @@ import {
1514 buildApplicationInternal ,
1615 emitFilesToDisk ,
1716} from '@angular/build/private' ;
18- import { BuilderContext , BuilderOutput } from '@angular-devkit/architect' ;
19- import { randomUUID } from 'crypto' ;
17+ import type { BuilderContext , BuilderOutput } from '@angular-devkit/architect' ;
2018import glob from 'fast-glob' ;
21- import * as fs from 'fs/promises' ;
22- import { IncomingMessage , ServerResponse } from 'http' ;
23- import type { Config , ConfigOptions , FilePattern , InlinePluginDef } from 'karma' ;
24- import * as path from 'path' ;
25- import { Observable , Subscriber , catchError , defaultIfEmpty , from , of , switchMap } from 'rxjs' ;
26- import { Configuration } from 'webpack' ;
27- import { ExecutionTransformer } from '../../transforms' ;
28- import { OutputHashing } from '../browser-esbuild/schema' ;
19+ import type { Config , ConfigOptions , FilePattern , InlinePluginDef , Server } from 'karma' ;
20+ import { randomUUID } from 'node:crypto' ;
21+ import * as fs from 'node:fs/promises' ;
22+ import type { IncomingMessage , ServerResponse } from 'node:http' ;
23+ import { createRequire } from 'node:module' ;
24+ import * as path from 'node:path' ;
25+ import { ReadableStreamController } from 'node:stream/web' ;
26+ import { BuildOutputFileType } from '../../tools/esbuild/bundler-context' ;
27+ import { OutputHashing } from '../application/schema' ;
2928import { findTests , getTestEntrypoints } from './find-tests' ;
3029import { Schema as KarmaBuilderOptions } from './schema' ;
3130
31+ const localResolve = createRequire ( __filename ) . resolve ;
32+
3233interface BuildOptions extends ApplicationBuilderInternalOptions {
3334 // We know that it's always a string since we set it.
3435 outputPath : string ;
@@ -170,7 +171,7 @@ function injectKarmaReporter(
170171 buildOptions : BuildOptions ,
171172 buildIterator : AsyncIterator < Result > ,
172173 karmaConfig : Config & ConfigOptions ,
173- subscriber : Subscriber < BuilderOutput > ,
174+ controller : ReadableStreamController < BuilderOutput > ,
174175) {
175176 const reporterName = 'angular-progress-notifier' ;
176177
@@ -204,7 +205,7 @@ function injectKarmaReporter(
204205 }
205206
206207 if ( buildOutput . kind === ResultKind . Failure ) {
207- subscriber . next ( { success : false , message : 'Build failed' } ) ;
208+ controller . enqueue ( { success : false , message : 'Build failed' } ) ;
208209 } else if (
209210 buildOutput . kind === ResultKind . Incremental ||
210211 buildOutput . kind === ResultKind . Full
@@ -226,9 +227,9 @@ function injectKarmaReporter(
226227
227228 onRunComplete = function ( _browsers : unknown , results : RunCompleteInfo ) {
228229 if ( results . exitCode === 0 ) {
229- subscriber . next ( { success : true } ) ;
230+ controller . enqueue ( { success : true } ) ;
230231 } else {
231- subscriber . next ( { success : false } ) ;
232+ controller . enqueue ( { success : false } ) ;
232233 }
233234 } ;
234235 }
@@ -254,44 +255,48 @@ export function execute(
254255 context : BuilderContext ,
255256 karmaOptions : ConfigOptions ,
256257 transforms : {
257- webpackConfiguration ?: ExecutionTransformer < Configuration > ;
258258 // The karma options transform cannot be async without a refactor of the builder implementation
259259 karmaOptions ?: ( options : ConfigOptions ) => ConfigOptions ;
260260 } = { } ,
261- ) : Observable < BuilderOutput > {
262- return from ( initializeApplication ( options , context , karmaOptions , transforms ) ) . pipe (
263- switchMap (
264- ( [ karma , karmaConfig , buildOptions , buildIterator ] ) =>
265- new Observable < BuilderOutput > ( ( subscriber ) => {
266- // If `--watch` is explicitly enabled or if we are keeping the Karma
267- // process running, we should hook Karma into the build.
268- if ( buildIterator ) {
269- injectKarmaReporter ( buildOptions , buildIterator , karmaConfig , subscriber ) ;
270- }
261+ ) : AsyncIterable < BuilderOutput > {
262+ let karmaServer : Server ;
263+
264+ return new ReadableStream ( {
265+ async start ( controller ) {
266+ let init ;
267+ try {
268+ init = await initializeApplication ( options , context , karmaOptions , transforms ) ;
269+ } catch ( err ) {
270+ if ( err instanceof ApplicationBuildError ) {
271+ controller . enqueue ( { success : false , message : err . message } ) ;
272+ controller . close ( ) ;
273+
274+ return ;
275+ }
271276
272- // Complete the observable once the Karma server returns.
273- const karmaServer = new karma . Server ( karmaConfig as Config , ( exitCode ) => {
274- subscriber . next ( { success : exitCode === 0 } ) ;
275- subscriber . complete ( ) ;
276- } ) ;
277+ throw err ;
278+ }
279+
280+ const [ karma , karmaConfig , buildOptions , buildIterator ] = init ;
277281
278- const karmaStart = karmaServer . start ( ) ;
279-
280- // Cleanup, signal Karma to exit.
281- return ( ) => {
282- void karmaStart . then ( ( ) => karmaServer . stop ( ) ) ;
283- } ;
284- } ) ,
285- ) ,
286- catchError ( ( err ) => {
287- if ( err instanceof ApplicationBuildError ) {
288- return of ( { success : false , message : err . message } ) ;
282+ // If `--watch` is explicitly enabled or if we are keeping the Karma
283+ // process running, we should hook Karma into the build.
284+ if ( buildIterator ) {
285+ injectKarmaReporter ( buildOptions , buildIterator , karmaConfig , controller ) ;
289286 }
290287
291- throw err ;
292- } ) ,
293- defaultIfEmpty ( { success : false } ) ,
294- ) ;
288+ // Close the stream once the Karma server returns.
289+ karmaServer = new karma . Server ( karmaConfig as Config , ( exitCode ) => {
290+ controller . enqueue ( { success : exitCode === 0 } ) ;
291+ controller . close ( ) ;
292+ } ) ;
293+
294+ await karmaServer . start ( ) ;
295+ } ,
296+ async cancel ( ) {
297+ await karmaServer ?. stop ( ) ;
298+ } ,
299+ } ) ;
295300}
296301
297302async function getProjectSourceRoot ( context : BuilderContext ) : Promise < string > {
@@ -314,10 +319,8 @@ function normalizePolyfills(polyfills: string | string[] | undefined): [string[]
314319 polyfills = [ ] ;
315320 }
316321
317- const jasmineGlobalEntryPoint =
318- '@angular-devkit/build-angular/src/builders/karma/jasmine_global.js' ;
319- const jasmineGlobalCleanupEntrypoint =
320- '@angular-devkit/build-angular/src/builders/karma/jasmine_global_cleanup.js' ;
322+ const jasmineGlobalEntryPoint = localResolve ( './polyfills/jasmine_global.js' ) ;
323+ const jasmineGlobalCleanupEntrypoint = localResolve ( './polyfills/jasmine_global_cleanup.js' ) ;
321324
322325 const zoneTestingEntryPoint = 'zone.js/testing' ;
323326 const polyfillsExludingZoneTesting = polyfills . filter ( ( p ) => p !== zoneTestingEntryPoint ) ;
@@ -351,18 +354,11 @@ async function initializeApplication(
351354 context : BuilderContext ,
352355 karmaOptions : ConfigOptions ,
353356 transforms : {
354- webpackConfiguration ?: ExecutionTransformer < Configuration > ;
355357 karmaOptions ?: ( options : ConfigOptions ) => ConfigOptions ;
356358 } = { } ,
357359) : Promise <
358360 [ typeof import ( 'karma' ) , Config & ConfigOptions , BuildOptions , AsyncIterator < Result > | null ]
359361> {
360- if ( transforms . webpackConfiguration ) {
361- context . logger . warn (
362- `This build is using the application builder but transforms.webpackConfiguration was provided. The transform will be ignored.` ,
363- ) ;
364- }
365-
366362 const outputPath = path . join ( context . workspaceRoot , 'dist/test-out' , randomUUID ( ) ) ;
367363 const projectSourceRoot = await getProjectSourceRoot ( context ) ;
368364
@@ -376,7 +372,7 @@ async function initializeApplication(
376372 if ( options . main ) {
377373 entryPoints . set ( mainName , options . main ) ;
378374 } else {
379- entryPoints . set ( mainName , '@angular-devkit/build-angular/src/builders/karma/ init_test_bed.js') ;
375+ entryPoints . set ( mainName , localResolve ( './polyfills/ init_test_bed.js') ) ;
380376 }
381377
382378 const instrumentForCoverage = options . codeCoverage
0 commit comments