11import chalk from 'chalk' ;
2- import { SudoRequestData , SudoRequestResponseData } from 'codify-schemas' ;
2+ import { SudoRequestData , SudoRequestResponseData } from 'codify-schemas' ;
33import { render } from 'ink' ;
44import { EventEmitter } from 'node:events' ;
55import React from 'react' ;
66
77import { Plan } from '../../entities/plan.js' ;
8- import { Event , ProcessName , SubProcessName , ctx } from '../../events/context.js' ;
8+ import { ctx , Event , ProcessName , SubProcessName } from '../../events/context.js' ;
99import { ImportResult , RequiredParameters , UserSuppliedParameters } from '../../orchestrators/import.js' ;
1010import { SudoUtils } from '../../utils/sudo.js' ;
1111import { DefaultComponent } from '../components/default-component.js' ;
1212import { ProgressState , ProgressStatus } from '../components/progress/progress-display.js' ;
1313import { RenderStatus , store } from '../store/index.js' ;
14- import { DisplayPlanStateTransition , RenderEvent , RenderState , Reporter } from './reporter.js' ;
14+ import { RenderEvent , RenderState , Reporter } from './reporter.js' ;
15+ import { sleep } from '../../utils/index.js' ;
1516
1617const ProgressLabelMapping = {
1718 [ ProcessName . APPLY ] : 'Codify apply' ,
@@ -75,57 +76,36 @@ export class DefaultReporter implements Reporter {
7576 password = await this . getUserPassword ( ) ;
7677 }
7778
78- const result = await SudoUtils . runCommand ( data . command , data . options , secureMode , pluginName , password )
79- this . renderEmitter . emit ( RenderEvent . PROMPT_SUDO_GRANTED ) ;
80-
81- return result ;
79+ return SudoUtils . runCommand ( data . command , data . options , secureMode , pluginName , password )
8280 }
8381
8482 displayPlan ( plan : Plan ) : void {
85- store . set ( store . renderState , { status : RenderStatus . DISPLAY_PLAN , data : plan } ) ;
83+ this . updateRenderState ( RenderStatus . DISPLAY_PLAN , plan )
8684 store . set ( store . progressState , null ) ;
8785 this . progressState = null ;
88-
89- // this.renderEmitter.emit(RenderEvent.STATE_TRANSITION, {
90- // nextState: RenderState.DISPLAY_PLAN,
91- // plan,
92- // } as DisplayPlanStateTransition);
9386 }
9487
9588 async promptConfirmation ( message : string ) : Promise < boolean > {
96- const result = await Promise . all ( [
97- new Promise < boolean > ( ( resolve ) => {
98- this . renderEmitter . once ( RenderEvent . PROMPT_CONFIRMATION_RESULT , ( isConfirmed ) => resolve ( isConfirmed as boolean ) ) ;
99- } ) ,
100-
101- store . set ( store . renderState , { status : RenderStatus . PROMPT_CONFIRMATION , data : message } )
102- // this.renderEmitter.emit(RenderEvent.STATE_TRANSITION, {
103- // nextState: RenderState.PROMPT_CONFIRMATION,
104- // message,
105- // }),
106- ] )
107-
108- const continueApply = result [ 0 ] ;
89+ const continueApply = await this . updateStateAndAwaitEvent < boolean > (
90+ ( ) => this . updateRenderState ( RenderStatus . PROMPT_CONFIRMATION , message ) ,
91+ RenderEvent . PROMPT_CONFIRMATION_RESULT
92+ )
10993
11094 if ( continueApply ) {
11195 // this.renderEmitter.emit(RenderEvent.STATE_TRANSITION, {
11296 // nextState: RenderState.APPLYING,
11397 // });
11498
115- store . set ( store . renderState , { status : RenderStatus . PROGRESS } ) ;
116-
99+ this . updateRenderState ( RenderStatus . PROGRESS )
117100 this . log ( `${ message } -> "Yes"` )
118101 }
119102
120103 return continueApply ;
121104 }
122105
123- displayApplyComplete ( messages : string [ ] ) : Promise < void > | void {
124- store . set ( store . renderState , { status : RenderStatus . APPLY_COMPLETE } ) ;
125-
126- // this.renderEmitter.emit(RenderEvent.STATE_TRANSITION, {
127- // nextState: RenderState.APPLY_COMPLETE,
128- // });
106+ async displayApplyComplete ( messages : string [ ] ) : Promise < void > {
107+ this . updateRenderState ( RenderStatus . APPLY_COMPLETE , messages ) ;
108+ await sleep ( 100 ) ; // This gives the renderer enough time to complete before the prompt exits
129109 }
130110
131111 private log ( args : string ) : void {
@@ -143,45 +123,33 @@ export class DefaultReporter implements Reporter {
143123 } ;
144124
145125 this . log ( `${ label } started` )
146-
147- store . set ( store . progressState , structuredClone ( this . progressState ) ) ;
148- // this.renderEmitter.emit(RenderEvent.PROGRESS_UPDATE, this.progressState);
126+ store . set ( store . progressState , this . progressState ) ;
149127 }
150128
151129 private onProcessFinishEvent ( name : ProcessName ) : void {
152130 const label = ProgressLabelMapping [ name ] ;
153-
154- this . progressState ! . status = ProgressStatus . FINISHED ;
155-
156131 this . log ( `${ label } finished successfully` )
157132
158- store . internal . set ( store . progressState , structuredClone ( this . progressState ) ) ;
133+ this . progressState ! . status = ProgressStatus . FINISHED ;
134+ store . set ( store . progressState , structuredClone ( this . progressState ) ) ;
159135 // this.renderEmitter.emit(RenderEvent.PROGRESS_UPDATE, this.progressState);
160136 }
161137
162138 private onSubprocessStartEvent ( name : SubProcessName , additionalName ?: string ) : void {
163- const label = ProgressLabelMapping [ name ] + ( additionalName
164- ? ' ' + additionalName
165- : ''
166- ) ;
139+ const label = ProgressLabelMapping [ name ] + ( additionalName ? ' ' + additionalName : '' ) ;
140+ this . log ( `${ label } started` )
167141
168142 this . progressState ?. subProgresses ?. push ( {
169143 label,
170144 name : name + additionalName ,
171145 status : ProgressStatus . IN_PROGRESS ,
172146 } ) ;
173-
174- this . log ( `${ label } started` )
175-
176147 store . set ( store . progressState , structuredClone ( this . progressState ) ) ;
177148 // this.renderEmitter.emit(RenderEvent.PROGRESS_UPDATE, this.progressState);
178149 }
179150
180151 private onSubprocessFinishEvent ( name : SubProcessName , additionalName ?: string ) : void {
181- const label = ProgressLabelMapping [ name ] + ( additionalName
182- ? ' ' + additionalName
183- : ''
184- ) ;
152+ const label = ProgressLabelMapping [ name ] + ( additionalName ? ' ' + additionalName : '' ) ;
185153
186154 const subProgress = this . progressState
187155 ?. subProgresses
@@ -202,12 +170,17 @@ export class DefaultReporter implements Reporter {
202170 let attemptCount = 0 ;
203171
204172 while ( attemptCount < 3 ) {
205- const passwordAttempt = await this . renderSudoPrompt ( attemptCount ) ;
173+ this . renderEmitter . emit ( RenderEvent . DISABLE_SUDO_PROMPT , false ) ;
174+ const passwordAttempt = await this . updateStateAndAwaitEvent < string > (
175+ ( ) => this . updateRenderState ( RenderStatus . SUDO_PROMPT , attemptCount ) ,
176+ RenderEvent . SUDO_PROMPT_RESULT ,
177+ ) ;
178+ this . renderEmitter . emit ( RenderEvent . DISABLE_SUDO_PROMPT , true ) ;
206179
207180 // Validates that the password works
208181 if ( SudoUtils . validate ( passwordAttempt ) ) {
209- this . renderEmitter . emit ( RenderEvent . PROMPT_SUDO_GRANTED ) ;
210- return passwordAttempt
182+ this . updateRenderState ( RenderStatus . PROGRESS )
183+ return passwordAttempt ;
211184 }
212185
213186 if ( attemptCount + 1 < 3 ) {
@@ -218,19 +191,11 @@ export class DefaultReporter implements Reporter {
218191 attemptCount ++ ;
219192 }
220193
221- this . renderEmitter . emit ( RenderEvent . PROMPT_SUDO_ERROR ) ;
194+ this . updateRenderState ( null )
195+ store . set ( store . renderState , { status : null } ) ;
222196 throw new Error ( 'sudo: 3 incorrect password attempts' )
223197 }
224198
225- private async renderSudoPrompt ( attemptCount : number ) : Promise < string > {
226- return new Promise ( ( resolve ) => {
227- this . renderEmitter . emit ( RenderEvent . PROMPT_SUDO , attemptCount ) ;
228- this . renderEmitter . on ( RenderEvent . PROMPT_SUDO_RESULT , ( password ) => {
229- resolve ( password )
230- } )
231- } )
232- }
233-
234199 private extractUserSuppliedParametersFromResult ( result : object ) : Map < string , Record < string , unknown > > {
235200 const resources = Object . entries ( result )
236201 . map ( ( [ key , value ] ) => {
@@ -251,4 +216,22 @@ export class DefaultReporter implements Reporter {
251216
252217 return new Map ( Object . entries ( resources ) ) ;
253218 }
219+
220+ private updateRenderState ( status : RenderStatus | null , data ?: unknown ) : void {
221+ store . set ( store . renderState , { status, data } ) ;
222+ }
223+
224+ // This is needed if we await any prompts. It makes the UI feel a lot more fluid since the await is no longer blocking
225+ private async updateStateAndAwaitEvent < T > ( fn : ( ) => void , eventName : string ) : Promise < T > {
226+ return ( await Promise . all ( [
227+ fn ( ) ,
228+ this . awaitEvent ( eventName )
229+ ] ) ) . at ( 1 ) as T ;
230+ }
231+
232+ private awaitEvent < T > ( name : string ) : Promise < T > {
233+ return new Promise ( ( resolve ) => {
234+ this . renderEmitter . once ( name , resolve )
235+ } ) ;
236+ }
254237}
0 commit comments