99import  {  BuildOutputFileType  }  from  '@angular/build' ; 
1010import  { 
1111 ApplicationBuilderInternalOptions , 
12+  Result , 
1213 ResultFile , 
1314 ResultKind , 
1415 buildApplicationInternal , 
@@ -42,6 +43,7 @@ class ApplicationBuildError extends Error {
4243function  injectKarmaReporter ( 
4344 context : BuilderContext , 
4445 buildOptions : BuildOptions , 
46+  buildIterator : AsyncIterator < Result > , 
4547 karmaConfig : Config  &  ConfigOptions , 
4648 subscriber : Subscriber < BuilderOutput > , 
4749)  { 
@@ -64,13 +66,15 @@ function injectKarmaReporter(
6466
6567 private  startWatchingBuild ( )  { 
6668 void  ( async  ( )  =>  { 
67-  for  await  ( const  buildOutput  of  buildApplicationInternal ( 
68-  { 
69-  ...buildOptions , 
70-  watch : true , 
71-  } , 
72-  context , 
73-  ) )  { 
69+  // This is effectively "for await of but skip what's already consumed". 
70+  let  isDone  =  false ;  // to mark the loop condition as "not constant". 
71+  while  ( ! isDone )  { 
72+  const  {  done,  value : buildOutput  }  =  await  buildIterator . next ( ) ; 
73+  if  ( done )  { 
74+  isDone  =  true ; 
75+  break ; 
76+  } 
77+ 
7478 if  ( buildOutput . kind  ===  ResultKind . Failure )  { 
7579 subscriber . next ( {  success : false ,  message : 'Build failed'  } ) ; 
7680 }  else  if  ( 
@@ -121,12 +125,12 @@ export function execute(
121125) : Observable < BuilderOutput >  { 
122126 return  from ( initializeApplication ( options ,  context ,  karmaOptions ,  transforms ) ) . pipe ( 
123127 switchMap ( 
124-  ( [ karma ,  karmaConfig ,  buildOptions ] )  => 
128+  ( [ karma ,  karmaConfig ,  buildOptions ,   buildIterator ] )  => 
125129 new  Observable < BuilderOutput > ( ( subscriber )  =>  { 
126130 // If `--watch` is explicitly enabled or if we are keeping the Karma 
127131 // process running, we should hook Karma into the build. 
128-  if  ( options . watch   ??   ! karmaConfig . singleRun )  { 
129-  injectKarmaReporter ( context ,  buildOptions ,  karmaConfig ,  subscriber ) ; 
132+  if  ( buildIterator )  { 
133+  injectKarmaReporter ( context ,  buildOptions ,  buildIterator ,   karmaConfig ,  subscriber ) ; 
130134 } 
131135
132136 // Complete the observable once the Karma server returns. 
@@ -199,7 +203,9 @@ async function initializeApplication(
199203 webpackConfiguration ?: ExecutionTransformer < Configuration > ; 
200204 karmaOptions ?: ( options : ConfigOptions )  =>  ConfigOptions ; 
201205 }  =  { } , 
202- ) : Promise < [ typeof  import ( 'karma' ) ,  Config  &  ConfigOptions ,  BuildOptions ] >  { 
206+ ) : Promise < 
207+  [ typeof  import ( 'karma' ) ,  Config  &  ConfigOptions ,  BuildOptions ,  AsyncIterator < Result >  |  null ] 
208+ >  { 
203209 if  ( transforms . webpackConfiguration )  { 
204210 context . logger . warn ( 
205211 `This build is using the application builder but transforms.webpackConfiguration was provided. The transform will be ignored.` , 
@@ -247,10 +253,14 @@ async function initializeApplication(
247253 styles : options . styles , 
248254 polyfills : normalizePolyfills ( options . polyfills ) , 
249255 webWorkerTsConfig : options . webWorkerTsConfig , 
256+  watch : options . watch  ??  ! karmaOptions . singleRun , 
250257 } ; 
251258
252259 // Build tests with `application` builder, using test files as entry points. 
253-  const  buildOutput  =  await  first ( buildApplicationInternal ( buildOptions ,  context ) ) ; 
260+  const  [ buildOutput ,  buildIterator ]  =  await  first ( 
261+  buildApplicationInternal ( buildOptions ,  context ) , 
262+  {  cancel : ! buildOptions . watch  } , 
263+  ) ; 
254264 if  ( buildOutput . kind  ===  ResultKind . Failure )  { 
255265 throw  new  ApplicationBuildError ( 'Build failed' ) ; 
256266 }  else  if  ( buildOutput . kind  !==  ResultKind . Full )  { 
@@ -265,28 +275,33 @@ async function initializeApplication(
265275 karmaOptions . files  ??=  [ ] ; 
266276 karmaOptions . files . push ( 
267277 // Serve polyfills first. 
268-  {  pattern : `${ outputPath }  /polyfills.js` ,  type : 'module'  } , 
278+  {  pattern : `${ outputPath }  /polyfills.js` ,  type : 'module' ,   watched :  false  } , 
269279 // Serve global setup script. 
270-  {  pattern : `${ outputPath }  /${ mainName }  .js` ,  type : 'module'  } , 
280+  {  pattern : `${ outputPath }  /${ mainName }  .js` ,  type : 'module' ,   watched :  false  } , 
271281 // Serve all source maps. 
272-  {  pattern : `${ outputPath }  /*.map` ,  included : false  } , 
282+  {  pattern : `${ outputPath }  /*.map` ,  included : false ,   watched :  false  } , 
273283 ) ; 
274284
275285 if  ( hasChunkOrWorkerFiles ( buildOutput . files ) )  { 
276286 karmaOptions . files . push ( 
277287 // Allow loading of chunk-* files but don't include them all on load. 
278-  {  pattern : `${ outputPath }  /{chunk,worker}-*.js` ,  type : 'module' ,  included : false  } , 
288+  { 
289+  pattern : `${ outputPath }  /{chunk,worker}-*.js` , 
290+  type : 'module' , 
291+  included : false , 
292+  watched : false , 
293+  } , 
279294 ) ; 
280295 } 
281296
282297 karmaOptions . files . push ( 
283298 // Serve remaining JS on page load, these are the test entrypoints. 
284-  {  pattern : `${ outputPath }  /*.js` ,  type : 'module'  } , 
299+  {  pattern : `${ outputPath }  /*.js` ,  type : 'module' ,   watched :  false  } , 
285300 ) ; 
286301
287302 if  ( options . styles ?. length )  { 
288303 // Serve CSS outputs on page load, these are the global styles. 
289-  karmaOptions . files . push ( {  pattern : `${ outputPath }  /*.css` ,  type : 'css'  } ) ; 
304+  karmaOptions . files . push ( {  pattern : `${ outputPath }  /*.css` ,  type : 'css' ,   watched :  false  } ) ; 
290305 } 
291306
292307 const  parsedKarmaConfig : Config  &  ConfigOptions  =  await  karma . config . parseConfig ( 
@@ -327,7 +342,7 @@ async function initializeApplication(
327342 parsedKarmaConfig . reporters  =  ( parsedKarmaConfig . reporters  ??  [ ] ) . concat ( [ 'coverage' ] ) ; 
328343 } 
329344
330-  return  [ karma ,  parsedKarmaConfig ,  buildOptions ] ; 
345+  return  [ karma ,  parsedKarmaConfig ,  buildOptions ,   buildIterator ] ; 
331346} 
332347
333348function  hasChunkOrWorkerFiles ( files : Record < string ,  unknown > ) : boolean  { 
@@ -364,9 +379,22 @@ export async function writeTestFiles(files: Record<string, ResultFile>, testDir:
364379} 
365380
366381/** Returns the first item yielded by the given generator and cancels the execution. */ 
367- async  function  first < T > ( generator : AsyncIterable < T > ) : Promise < T >  { 
382+ async  function  first < T > ( 
383+  generator : AsyncIterable < T > , 
384+  {  cancel } : {  cancel : boolean  } , 
385+ ) : Promise < [ T ,  AsyncIterator < T >  |  null ] >  { 
386+  if  ( ! cancel )  { 
387+  const  iterator : AsyncIterator < T >  =  generator [ Symbol . asyncIterator ] ( ) ; 
388+  const  firstValue  =  await  iterator . next ( ) ; 
389+  if  ( firstValue . done )  { 
390+  throw  new  Error ( 'Expected generator to emit at least once.' ) ; 
391+  } 
392+ 
393+  return  [ firstValue . value ,  iterator ] ; 
394+  } 
395+ 
368396 for  await  ( const  value  of  generator )  { 
369-  return  value ; 
397+  return  [ value ,   null ] ; 
370398 } 
371399
372400 throw  new  Error ( 'Expected generator to emit at least once.' ) ; 
0 commit comments