@@ -2,36 +2,80 @@ import type { Readable } from 'node:stream';
2
2
import { on } from 'node:events' ;
3
3
import { setTimeout } from 'node:timers/promises' ;
4
4
5
- type MaybePromise < T > = T | Promise < T > ;
5
+ type OnTimeoutCallback = ( ) => void ;
6
6
7
- export const processInteract = async (
8
- stdout : Readable ,
9
- actions : ( ( data : string ) => MaybePromise < boolean | void > ) [ ] ,
7
+ type Api = {
8
+ startTime : number ;
9
+ onTimeout : ( callback : OnTimeoutCallback ) => void ;
10
+ } ;
11
+
12
+ const enforceTimeout = < ReturnType > (
10
13
timeout : number ,
11
- ) => {
14
+ function_ : ( api : Api ) => ReturnType ,
15
+ ) : ReturnType => {
12
16
const startTime = Date . now ( ) ;
13
- const logs : [ time : number , string ] [ ] = [ ] ;
17
+ let onTimeoutCallback : OnTimeoutCallback ;
14
18
15
- let currentAction = actions . shift ( ) ;
19
+ const runFunction = function_ ( {
20
+ startTime,
21
+ onTimeout : ( callback ) => {
22
+ onTimeoutCallback = callback ;
23
+ } ,
24
+ } ) ;
25
+
26
+ if ( ! ( runFunction instanceof Promise ) ) {
27
+ return runFunction ;
28
+ }
16
29
17
30
const ac = new AbortController ( ) ;
18
- setTimeout ( timeout , true , ac ) . then (
19
- ( ) => {
20
- if ( currentAction ) {
21
- console . error ( `Timeout ${ timeout } ms exceeded:` ) ;
22
- console . log ( logs ) ;
31
+ const timer = setTimeout ( timeout , true , ac ) . then (
32
+ async ( ) => {
33
+ if ( onTimeoutCallback ) {
34
+ await onTimeoutCallback ( ) ;
23
35
}
36
+
37
+ throw new Error ( 'Timeout' ) ;
24
38
} ,
25
- ( ) => { } ,
39
+ ( ) => { /* Timeout aborted */ } ,
26
40
) ;
27
41
42
+ return Promise . race ( [
43
+ runFunction . finally ( ( ) => ac . abort ( ) ) ,
44
+ timer ,
45
+ ] ) as ReturnType ;
46
+ } ;
47
+
48
+ type MaybePromise < T > = T | Promise < T > ;
49
+
50
+ export const processInteract = async (
51
+ stdout : Readable ,
52
+ actions : ( ( data : string ) => MaybePromise < boolean | void > ) [ ] ,
53
+ timeout : number ,
54
+ ) => enforceTimeout ( timeout , async ( { startTime, onTimeout } ) => {
55
+ const logs : {
56
+ time : number ;
57
+ stdout : string ;
58
+ } [ ] = [ ] ;
59
+
60
+ let currentAction = actions . shift ( ) ;
61
+
62
+ onTimeout ( ( ) => {
63
+ if ( currentAction ) {
64
+ const error = Object . assign (
65
+ new Error ( `Timeout ${ timeout } ms exceeded:` ) ,
66
+ { logs } ,
67
+ ) ;
68
+ throw error ;
69
+ }
70
+ } ) ;
71
+
28
72
while ( currentAction ) {
29
73
for await ( const [ chunk ] of on ( stdout , 'data' ) ) {
30
74
const chunkString = chunk . toString ( ) ;
31
- logs . push ( [
32
- Date . now ( ) - startTime ,
33
- chunkString ,
34
- ] ) ;
75
+ logs . push ( {
76
+ time : Date . now ( ) - startTime ,
77
+ stdout : chunkString ,
78
+ } ) ;
35
79
36
80
const gotoNextAction = await currentAction ( chunkString ) ;
37
81
if ( gotoNextAction ) {
@@ -40,5 +84,4 @@ export const processInteract = async (
40
84
}
41
85
}
42
86
}
43
- ac . abort ( ) ;
44
- } ;
87
+ } ) ;
0 commit comments