@@ -13,6 +13,14 @@ import {
13
13
type ITerminal ,
14
14
type IPackageJson
15
15
} from '@rushstack/node-core-library' ;
16
+ import {
17
+ type IOperationExecutionOptions ,
18
+ type IWatchLoopState ,
19
+ Operation ,
20
+ OperationExecutionManager ,
21
+ OperationStatus ,
22
+ WatchLoop
23
+ } from '@rushstack/operation-graph' ;
16
24
import type {
17
25
CommandLineFlagParameter ,
18
26
CommandLineParameterProvider ,
@@ -24,11 +32,6 @@ import type { HeftConfiguration } from '../configuration/HeftConfiguration';
24
32
import type { LoggingManager } from '../pluginFramework/logging/LoggingManager' ;
25
33
import type { MetricsCollector } from '../metrics/MetricsCollector' ;
26
34
import { HeftParameterManager } from '../pluginFramework/HeftParameterManager' ;
27
- import {
28
- OperationExecutionManager ,
29
- type IOperationExecutionOptions
30
- } from '../operations/OperationExecutionManager' ;
31
- import { Operation } from '../operations/Operation' ;
32
35
import { TaskOperationRunner } from '../operations/runners/TaskOperationRunner' ;
33
36
import { PhaseOperationRunner } from '../operations/runners/PhaseOperationRunner' ;
34
37
import type { HeftPhase } from '../pluginFramework/HeftPhase' ;
@@ -41,10 +44,8 @@ import type {
41
44
} from '../pluginFramework/HeftLifecycleSession' ;
42
45
import type { HeftLifecycle } from '../pluginFramework/HeftLifecycle' ;
43
46
import type { HeftTask } from '../pluginFramework/HeftTask' ;
44
- import { CancellationToken , CancellationTokenSource } from '../pluginFramework/CancellationToken' ;
45
47
import { deleteFilesAsync , type IDeleteOperation } from '../plugins/DeleteFilesPlugin' ;
46
48
import { Constants } from '../utilities/Constants' ;
47
- import { OperationStatus } from '../operations/OperationStatus' ;
48
49
49
50
export interface IHeftActionRunnerOptions extends IHeftActionOptions {
50
51
action : IHeftAction ;
@@ -81,15 +82,18 @@ export async function runWithLoggingAsync(
81
82
loggingManager : LoggingManager ,
82
83
terminal : ITerminal ,
83
84
metricsCollector : MetricsCollector ,
84
- cancellationToken : CancellationToken
85
- ) : Promise < void > {
85
+ abortSignal : AbortSignal ,
86
+ throwOnFailure ?: boolean
87
+ ) : Promise < OperationStatus > {
86
88
const startTime : number = performance . now ( ) ;
87
89
loggingManager . resetScopedLoggerErrorsAndWarnings ( ) ;
88
90
91
+ let result : OperationStatus = OperationStatus . Failure ;
92
+
89
93
// Execute the action operations
90
94
let encounteredError : boolean = false ;
91
95
try {
92
- const result : OperationStatus = await fn ( ) ;
96
+ result = await fn ( ) ;
93
97
if ( result === OperationStatus . Failure ) {
94
98
encounteredError = true ;
95
99
}
@@ -100,8 +104,8 @@ export async function runWithLoggingAsync(
100
104
const warningStrings : string [ ] = loggingManager . getWarningStrings ( ) ;
101
105
const errorStrings : string [ ] = loggingManager . getErrorStrings ( ) ;
102
106
103
- const wasCancelled : boolean = cancellationToken . isCancelled ;
104
- const encounteredWarnings : boolean = warningStrings . length > 0 || wasCancelled ;
107
+ const wasAborted : boolean = abortSignal . aborted ;
108
+ const encounteredWarnings : boolean = warningStrings . length > 0 || wasAborted ;
105
109
encounteredError = encounteredError || errorStrings . length > 0 ;
106
110
107
111
await metricsCollector . recordAsync (
@@ -112,7 +116,7 @@ export async function runWithLoggingAsync(
112
116
action . getParameterStringMap ( )
113
117
) ;
114
118
115
- const finishedLoggingWord : string = encounteredError ? 'Failed' : wasCancelled ? 'Cancelled ' : 'Finished' ;
119
+ const finishedLoggingWord : string = encounteredError ? 'Failed' : wasAborted ? 'Aborted ' : 'Finished' ;
116
120
const duration : number = performance . now ( ) - startTime ;
117
121
const durationSeconds : number = Math . round ( duration ) / 1000 ;
118
122
const finishedLoggingLine : string = `-------------------- ${ finishedLoggingWord } (${ durationSeconds } s) --------------------` ;
@@ -143,9 +147,11 @@ export async function runWithLoggingAsync(
143
147
}
144
148
}
145
149
146
- if ( encounteredError ) {
150
+ if ( encounteredError && throwOnFailure ) {
147
151
throw new AlreadyReportedError ( ) ;
148
152
}
153
+
154
+ return result ;
149
155
}
150
156
151
157
export class HeftActionRunner {
@@ -260,41 +266,30 @@ export class HeftActionRunner {
260
266
// executed, and the session should be populated with the executing parameters.
261
267
this . _internalHeftSession . parameterManager = this . parameterManager ;
262
268
263
- // Set up the ability to terminate the build via Ctrl+C and have it exit gracefully if pressed once,
264
- // less gracefully if pressed a second time.
265
- const cliCancellationTokenSource : CancellationTokenSource = new CancellationTokenSource ( ) ;
266
- const cliCancellationToken : CancellationToken = cliCancellationTokenSource . token ;
267
- const cli : ReadlineInterface = createInterface ( process . stdin , undefined , undefined , true ) ;
268
- let forceTerminate : boolean = false ;
269
- cli . on ( 'SIGINT' , ( ) => {
270
- cli . close ( ) ;
271
-
272
- if ( forceTerminate ) {
273
- terminal . writeErrorLine ( `Forcibly terminating.` ) ;
274
- process . exit ( 1 ) ;
275
- } else {
276
- terminal . writeLine (
277
- Colors . yellow ( Colors . bold ( `Canceling build... Press Ctrl+C again to forcibly terminate.` ) )
278
- ) ;
279
- }
280
-
281
- forceTerminate = true ;
282
- cliCancellationTokenSource . cancel ( ) ;
283
- } ) ;
284
-
285
269
initializeHeft ( this . _heftConfiguration , terminal , this . parameterManager . defaultParameters . verbose ) ;
286
270
287
271
const operations : ReadonlySet < Operation > = this . _generateOperations ( ) ;
288
272
289
273
const executionManager : OperationExecutionManager = new OperationExecutionManager ( operations ) ;
290
274
275
+ const cliAbortSignal : AbortSignal = this . _createCliAbortSignal ( ) ;
276
+
291
277
try {
292
278
await _startLifecycleAsync ( this . _internalHeftSession ) ;
293
279
294
280
if ( this . _action . watch ) {
295
- await this . _executeWatchAsync ( executionManager , cliCancellationToken ) ;
281
+ const watchLoop : WatchLoop = this . _createWatchLoop ( executionManager ) ;
282
+
283
+ if ( process . send ) {
284
+ await watchLoop . runIPCAsync ( ) ;
285
+ } else {
286
+ await watchLoop . runUntilAbortedAsync ( cliAbortSignal , ( ) => {
287
+ terminal . writeLine ( Colors . bold ( 'Waiting for changes. Press CTRL + C to exit...' ) ) ;
288
+ terminal . writeLine ( '' ) ;
289
+ } ) ;
290
+ }
296
291
} else {
297
- await this . _executeOnceAsync ( executionManager , cliCancellationToken ) ;
292
+ await this . _executeOnceAsync ( executionManager , cliAbortSignal ) ;
298
293
}
299
294
} finally {
300
295
// Invoke this here both to ensure it always runs and that it does so after recordMetrics
@@ -305,96 +300,67 @@ export class HeftActionRunner {
305
300
}
306
301
}
307
302
308
- private async _executeWatchAsync (
309
- executionManager : OperationExecutionManager ,
310
- cliCancellationToken : CancellationToken
311
- ) : Promise < void > {
312
- let runRequested : boolean = true ;
313
- let isRunning : boolean = true ;
314
- let cancellationTokenSource : CancellationTokenSource = new CancellationTokenSource ( ) ;
315
-
316
- const { _terminal : terminal } = this ;
317
-
318
- let resolveRequestRun ! : ( requestor ?: string ) => void ;
319
- function createRequestRunPromise ( ) : Promise < void > {
320
- return new Promise < string | undefined > (
321
- ( resolve : ( requestor ?: string ) => void , reject : ( err : Error ) => void ) => {
322
- resolveRequestRun = resolve ;
323
- }
324
- ) . then ( ( requestor : string | undefined ) => {
325
- terminal . writeLine ( Colors . bold ( `New run requested by ${ requestor || 'unknown task' } ` ) ) ;
326
- runRequested = true ;
327
- if ( isRunning ) {
328
- terminal . writeLine ( Colors . bold ( `Cancelling incremental build...` ) ) ;
329
- // If there's a source file change, we need to cancel the incremental build and wait for the
330
- // execution to finish before we begin execution again.
331
- cancellationTokenSource . cancel ( ) ;
332
- }
333
- } ) ;
334
- }
335
- let requestRunPromise : Promise < void > = createRequestRunPromise ( ) ;
336
-
337
- function cancelExecution ( ) : void {
338
- cancellationTokenSource . cancel ( ) ;
339
- }
340
-
341
- function requestRun ( requestor ?: string ) : void {
342
- // The wrapper here allows operation runners to hang onto a single instance, despite the underlying
343
- // promise changing.
344
- resolveRequestRun ( requestor ) ;
345
- }
303
+ private _createCliAbortSignal ( ) : AbortSignal {
304
+ // Set up the ability to terminate the build via Ctrl+C and have it exit gracefully if pressed once,
305
+ // less gracefully if pressed a second time.
306
+ const cliAbortController : AbortController = new AbortController ( ) ;
307
+ const cliAbortSignal : AbortSignal = cliAbortController . signal ;
308
+ const cli : ReadlineInterface = createInterface ( process . stdin , undefined , undefined , true ) ;
309
+ const terminal : ITerminal = this . _terminal ;
310
+ let forceTerminate : boolean = false ;
311
+ cli . on ( 'SIGINT' , ( ) => {
312
+ cli . close ( ) ;
346
313
347
- // eslint-disable-next-line no-constant-condition
348
- while ( ! cliCancellationToken . isCancelled ) {
349
- if ( cancellationTokenSource . isCancelled ) {
350
- cancellationTokenSource = new CancellationTokenSource ( ) ;
351
- cliCancellationToken . onCancelledPromise . finally ( cancelExecution ) ;
314
+ if ( forceTerminate ) {
315
+ terminal . writeErrorLine ( `Forcibly terminating.` ) ;
316
+ process . exit ( 1 ) ;
317
+ } else {
318
+ terminal . writeLine (
319
+ Colors . yellow ( Colors . bold ( `Canceling build... Press Ctrl+C again to forcibly terminate.` ) )
320
+ ) ;
352
321
}
353
322
354
- // Create the cancellation token which is passed to the incremental build.
355
- const cancellationToken : CancellationToken = cancellationTokenSource . token ;
356
-
357
- // Write an empty line to the terminal for separation between iterations. We've already iterated
358
- // at this point, so log out that we're about to start a new run.
359
- terminal . writeLine ( '' ) ;
360
- terminal . writeLine ( Colors . bold ( 'Starting incremental build...' ) ) ;
361
-
362
- // Start the incremental build and wait for a source file to change
363
- runRequested = false ;
364
- isRunning = true ;
323
+ forceTerminate = true ;
324
+ cliAbortController . abort ( ) ;
325
+ } ) ;
365
326
366
- try {
367
- await this . _executeOnceAsync ( executionManager , cancellationToken , requestRun ) ;
368
- } catch ( err ) {
369
- if ( ! ( err instanceof AlreadyReportedError ) ) {
370
- throw err ;
371
- }
372
- } finally {
373
- isRunning = false ;
374
- }
327
+ return cliAbortSignal ;
328
+ }
375
329
376
- if ( ! runRequested ) {
377
- terminal . writeLine ( Colors . bold ( 'Waiting for changes. Press CTRL + C to exit...' ) ) ;
330
+ private _createWatchLoop ( executionManager : OperationExecutionManager ) : WatchLoop {
331
+ const { _terminal : terminal } = this ;
332
+ const watchLoop : WatchLoop = new WatchLoop ( {
333
+ onBeforeExecute : ( ) => {
334
+ // Write an empty line to the terminal for separation between iterations. We've already iterated
335
+ // at this point, so log out that we're about to start a new run.
378
336
terminal . writeLine ( '' ) ;
379
- await Promise . race ( [ requestRunPromise , cliCancellationToken . onCancelledPromise ] ) ;
337
+ terminal . writeLine ( Colors . bold ( 'Starting incremental build...' ) ) ;
338
+ } ,
339
+ executeAsync : ( state : IWatchLoopState ) : Promise < OperationStatus > => {
340
+ return this . _executeOnceAsync ( executionManager , state . abortSignal , state . requestRun ) ;
341
+ } ,
342
+ onRequestRun : ( requestor ?: string ) => {
343
+ terminal . writeLine ( Colors . bold ( `New run requested by ${ requestor || 'unknown task' } ` ) ) ;
344
+ } ,
345
+ onAbort : ( ) => {
346
+ terminal . writeLine ( Colors . bold ( `Cancelling incremental build...` ) ) ;
380
347
}
381
-
382
- requestRunPromise = createRequestRunPromise ( ) ;
383
- }
348
+ } ) ;
349
+ return watchLoop ;
384
350
}
385
351
386
352
private async _executeOnceAsync (
387
353
executionManager : OperationExecutionManager ,
388
- cancellationToken : CancellationToken ,
354
+ abortSignal : AbortSignal ,
389
355
requestRun ?: ( requestor ?: string ) => void
390
- ) : Promise < void > {
356
+ ) : Promise < OperationStatus > {
391
357
// Execute the action operations
392
- await runWithLoggingAsync (
358
+ return await runWithLoggingAsync (
393
359
( ) => {
394
360
const operationExecutionManagerOptions : IOperationExecutionOptions = {
395
361
terminal : this . _terminal ,
396
362
parallelism : this . _parallelism ,
397
- cancellationToken ,
363
+ abortSignal ,
398
364
requestRun
399
365
} ;
400
366
@@ -404,7 +370,8 @@ export class HeftActionRunner {
404
370
this . _loggingManager ,
405
371
this . _terminal ,
406
372
this . _metricsCollector ,
407
- cancellationToken
373
+ abortSignal ,
374
+ ! requestRun
408
375
) ;
409
376
}
410
377
0 commit comments