6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
- import { BuildOutputFileType } from '@angular/build' ;
10
9
import {
11
10
ApplicationBuilderInternalOptions ,
12
11
Result ,
@@ -15,21 +14,22 @@ import {
15
14
buildApplicationInternal ,
16
15
emitFilesToDisk ,
17
16
} from '@angular/build/private' ;
18
- import { BuilderContext , BuilderOutput } from '@angular-devkit/architect' ;
17
+ import type { BuilderContext , BuilderOutput } from '@angular-devkit/architect' ;
19
18
import glob from 'fast-glob' ;
20
- import type { Config , ConfigOptions , FilePattern , InlinePluginDef } from 'karma' ;
19
+ import type { Config , ConfigOptions , FilePattern , InlinePluginDef , Server } from 'karma' ;
21
20
import { randomUUID } from 'node:crypto' ;
22
21
import * as fs from 'node:fs/promises' ;
23
- import { IncomingMessage , ServerResponse } from 'node:http' ;
22
+ import type { IncomingMessage , ServerResponse } from 'node:http' ;
23
+ import { createRequire } from 'node:module' ;
24
24
import * as path from 'node:path' ;
25
- import { Observable , Subscriber , catchError , defaultIfEmpty , from , of , switchMap } from 'rxjs' ;
26
- import { Configuration } from 'webpack' ;
27
- import { ExecutionTransformer } from '../../transforms' ;
28
- import { normalizeFileReplacements } from '../../utils' ;
29
- import { OutputHashing } from '../browser-esbuild/schema' ;
25
+ import { ReadableStreamController } from 'node:stream/web' ;
26
+ import { BuildOutputFileType } from '../../tools/esbuild/bundler-context' ;
27
+ import { OutputHashing } from '../application/schema' ;
30
28
import { findTests , getTestEntrypoints } from './find-tests' ;
31
29
import { Schema as KarmaBuilderOptions } from './schema' ;
32
30
31
+ const localResolve = createRequire ( __filename ) . resolve ;
32
+
33
33
interface BuildOptions extends ApplicationBuilderInternalOptions {
34
34
// We know that it's always a string since we set it.
35
35
outputPath : string ;
@@ -171,7 +171,7 @@ function injectKarmaReporter(
171
171
buildOptions : BuildOptions ,
172
172
buildIterator : AsyncIterator < Result > ,
173
173
karmaConfig : Config & ConfigOptions ,
174
- subscriber : Subscriber < BuilderOutput > ,
174
+ controller : ReadableStreamController < BuilderOutput > ,
175
175
) {
176
176
const reporterName = 'angular-progress-notifier' ;
177
177
@@ -205,7 +205,7 @@ function injectKarmaReporter(
205
205
}
206
206
207
207
if ( buildOutput . kind === ResultKind . Failure ) {
208
- subscriber . next ( { success : false , message : 'Build failed' } ) ;
208
+ controller . enqueue ( { success : false , message : 'Build failed' } ) ;
209
209
} else if (
210
210
buildOutput . kind === ResultKind . Incremental ||
211
211
buildOutput . kind === ResultKind . Full
@@ -227,9 +227,9 @@ function injectKarmaReporter(
227
227
228
228
onRunComplete = function ( _browsers : unknown , results : RunCompleteInfo ) {
229
229
if ( results . exitCode === 0 ) {
230
- subscriber . next ( { success : true } ) ;
230
+ controller . enqueue ( { success : true } ) ;
231
231
} else {
232
- subscriber . next ( { success : false } ) ;
232
+ controller . enqueue ( { success : false } ) ;
233
233
}
234
234
} ;
235
235
}
@@ -255,44 +255,48 @@ export function execute(
255
255
context : BuilderContext ,
256
256
karmaOptions : ConfigOptions ,
257
257
transforms : {
258
- webpackConfiguration ?: ExecutionTransformer < Configuration > ;
259
258
// The karma options transform cannot be async without a refactor of the builder implementation
260
259
karmaOptions ?: ( options : ConfigOptions ) => ConfigOptions ;
261
260
} = { } ,
262
- ) : Observable < BuilderOutput > {
263
- return from ( initializeApplication ( options , context , karmaOptions , transforms ) ) . pipe (
264
- switchMap (
265
- ( [ karma , karmaConfig , buildOptions , buildIterator ] ) =>
266
- new Observable < BuilderOutput > ( ( subscriber ) => {
267
- // If `--watch` is explicitly enabled or if we are keeping the Karma
268
- // process running, we should hook Karma into the build.
269
- if ( buildIterator ) {
270
- injectKarmaReporter ( buildOptions , buildIterator , karmaConfig , subscriber ) ;
271
- }
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
+ }
272
276
273
- // Complete the observable once the Karma server returns.
274
- const karmaServer = new karma . Server ( karmaConfig as Config , ( exitCode ) => {
275
- subscriber . next ( { success : exitCode === 0 } ) ;
276
- subscriber . complete ( ) ;
277
- } ) ;
277
+ throw err ;
278
+ }
279
+
280
+ const [ karma , karmaConfig , buildOptions , buildIterator ] = init ;
278
281
279
- const karmaStart = karmaServer . start ( ) ;
280
-
281
- // Cleanup, signal Karma to exit.
282
- return ( ) => {
283
- void karmaStart . then ( ( ) => karmaServer . stop ( ) ) ;
284
- } ;
285
- } ) ,
286
- ) ,
287
- catchError ( ( err ) => {
288
- if ( err instanceof ApplicationBuildError ) {
289
- 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 ) ;
290
286
}
291
287
292
- throw err ;
293
- } ) ,
294
- defaultIfEmpty ( { success : false } ) ,
295
- ) ;
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
+ } ) ;
296
300
}
297
301
298
302
async function getProjectSourceRoot ( context : BuilderContext ) : Promise < string > {
@@ -315,10 +319,8 @@ function normalizePolyfills(polyfills: string | string[] | undefined): [string[]
315
319
polyfills = [ ] ;
316
320
}
317
321
318
- const jasmineGlobalEntryPoint =
319
- '@angular-devkit/build-angular/src/builders/karma/jasmine_global.js' ;
320
- const jasmineGlobalCleanupEntrypoint =
321
- '@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' ) ;
322
324
323
325
const zoneTestingEntryPoint = 'zone.js/testing' ;
324
326
const polyfillsExludingZoneTesting = polyfills . filter ( ( p ) => p !== zoneTestingEntryPoint ) ;
@@ -352,18 +354,11 @@ async function initializeApplication(
352
354
context : BuilderContext ,
353
355
karmaOptions : ConfigOptions ,
354
356
transforms : {
355
- webpackConfiguration ?: ExecutionTransformer < Configuration > ;
356
357
karmaOptions ?: ( options : ConfigOptions ) => ConfigOptions ;
357
358
} = { } ,
358
359
) : Promise <
359
360
[ typeof import ( 'karma' ) , Config & ConfigOptions , BuildOptions , AsyncIterator < Result > | null ]
360
361
> {
361
- if ( transforms . webpackConfiguration ) {
362
- context . logger . warn (
363
- `This build is using the application builder but transforms.webpackConfiguration was provided. The transform will be ignored.` ,
364
- ) ;
365
- }
366
-
367
362
const outputPath = path . join ( context . workspaceRoot , 'dist/test-out' , randomUUID ( ) ) ;
368
363
const projectSourceRoot = await getProjectSourceRoot ( context ) ;
369
364
@@ -377,7 +372,7 @@ async function initializeApplication(
377
372
if ( options . main ) {
378
373
entryPoints . set ( mainName , options . main ) ;
379
374
} else {
380
- entryPoints . set ( mainName , '@angular-devkit/build-angular/src/builders/karma/ init_test_bed.js') ;
375
+ entryPoints . set ( mainName , localResolve ( './polyfills/ init_test_bed.js') ) ;
381
376
}
382
377
383
378
const instrumentForCoverage = options . codeCoverage
@@ -416,9 +411,10 @@ async function initializeApplication(
416
411
watch : options . watch ?? ! karmaOptions . singleRun ,
417
412
stylePreprocessorOptions : options . stylePreprocessorOptions ,
418
413
inlineStyleLanguage : options . inlineStyleLanguage ,
419
- fileReplacements : options . fileReplacements
420
- ? normalizeFileReplacements ( options . fileReplacements , './' )
421
- : undefined ,
414
+ fileReplacements : options . fileReplacements ,
415
+ define : options . define ,
416
+ loader : options . loader ,
417
+ externalDependencies : options . externalDependencies ,
422
418
} ;
423
419
424
420
// Build tests with `application` builder, using test files as entry points.
0 commit comments