1
+ import { ChildProcessWithoutNullStreams , spawn } from "child_process" ;
2
+ import {
3
+ DaemonEvent ,
4
+ DaemonMessage ,
5
+ DaemonRequest ,
6
+ DaemonResponse ,
7
+ isDaemonEvent ,
8
+ isDaemonRequest ,
9
+ isDaemonResponse ,
10
+ isReadyDaemonEvent ,
11
+ } from "./protocol" ;
12
+ import { EventEmitter } from "events" ;
13
+
14
+ /**
15
+ * The types of events that are emitted by the {@link DartFrogDaemon}.
16
+ *
17
+ * The possible types of events are:
18
+ * - "request": When a request is sent to the Dart Frog daemon. The
19
+ * {@link DaemonRequest} is passed as an argument to the event handler.
20
+ * - "response": When a response is received from the Dart Frog daemon. The
21
+ * {@link DaemonResponse} is passed as an argument to the event handler.
22
+ * - "event": When an event is received from the Dart Frog daemon. The
23
+ * {@link DaemonEvent} is passed as an argument to the event handler.
24
+ */
25
+ export enum DartFrogDaemonEventEmitterTypes {
26
+ request = "request" ,
27
+ response = "response" ,
28
+ event = "event" ,
29
+ }
30
+
1
31
/**
2
32
* The Dart Frog daemon is a long-running process that is responsible for
3
33
* managing a single or multiple Dart Frog projects simultaneously.
@@ -15,4 +45,128 @@ export class DartFrogDaemon {
15
45
public static get instance ( ) {
16
46
return this . _instance || ( this . _instance = new this ( ) ) ;
17
47
}
48
+
49
+ private daemonMessagesEventEmitter = new EventEmitter ( ) ;
50
+
51
+ /**
52
+ * The process that is running the Dart Frog daemon.
53
+ *
54
+ * Undefined until the Dart Frog daemon is {@link invoke}d.
55
+ */
56
+ private process : ChildProcessWithoutNullStreams | undefined ;
57
+
58
+ private _isReady : boolean = false ;
59
+
60
+ /**
61
+ * Whether the Dart Frog daemon is ready to accept requests.
62
+ *
63
+ * The Dart Frog daemon is ready to accept requests when it has emmitted
64
+ * the "ready" event after being {@link invoke}d.
65
+ *
66
+ * @see {@link invoke } to invoke the Dart Frog daemon.
67
+ */
68
+ public get isReady ( ) : boolean {
69
+ return this . _isReady ;
70
+ }
71
+
72
+ /**
73
+ * Invokes the Dart Frog daemon.
74
+ *
75
+ * If the Dart Frog daemon is already running, this method will immediately
76
+ * return.
77
+ *
78
+ * After invoking the Dart Frog daemon, it will be ready to accept requests.
79
+ *
80
+ * @param workingDirectory The working directory of the Dart Frog daemon,
81
+ * usually the root directory of the Dart Frog project.
82
+ */
83
+ public async invoke ( workingDirectory : string ) : Promise < void > {
84
+ if ( this . isReady || this . process ) {
85
+ return Promise . resolve ( ) ;
86
+ }
87
+
88
+ const readyPromise = new Promise < void > ( ( resolve ) => {
89
+ const readyEventListener = ( message : DaemonEvent ) => {
90
+ if ( ! this . _isReady && isReadyDaemonEvent ( message ) ) {
91
+ this . _isReady = true ;
92
+ resolve ( ) ;
93
+ this . off ( DartFrogDaemonEventEmitterTypes . event , readyEventListener ) ;
94
+ }
95
+ } ;
96
+ this . on (
97
+ DartFrogDaemonEventEmitterTypes . event ,
98
+ readyEventListener . bind ( this )
99
+ ) ;
100
+ } ) ;
101
+
102
+ this . process = spawn ( "dart_frog" , [ "daemon" ] , {
103
+ cwd : workingDirectory ,
104
+ } ) ;
105
+ this . process . stdout . on ( "data" , this . stdoutDataListener . bind ( this ) ) ;
106
+
107
+ return readyPromise ;
108
+ }
109
+
110
+ /**
111
+ * Decodes the stdout and emits events accordingly via the
112
+ * {@link daemonMessagesEventEmitter}.
113
+ *
114
+ * @param data The data that was received from the stdout of the Dart Frog
115
+ * Daemon.
116
+ * @see {@link daemonMessagesEventEmitter } for listening to the events that
117
+ * are emitted.
118
+ */
119
+ private stdoutDataListener ( data : Buffer ) : void {
120
+ const daemonMessages = DaemonMessage . decode ( data ) ;
121
+ for ( const message of daemonMessages ) {
122
+ if ( isDaemonEvent ( message ) ) {
123
+ this . daemonMessagesEventEmitter . emit (
124
+ DartFrogDaemonEventEmitterTypes . event ,
125
+ message
126
+ ) ;
127
+ } else if ( isDaemonResponse ( message ) ) {
128
+ this . daemonMessagesEventEmitter . emit (
129
+ DartFrogDaemonEventEmitterTypes . response ,
130
+ message
131
+ ) ;
132
+ } else if ( isDaemonRequest ( message ) ) {
133
+ this . daemonMessagesEventEmitter . emit (
134
+ DartFrogDaemonEventEmitterTypes . request ,
135
+ message
136
+ ) ;
137
+ }
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Starts listening to events related to this Dart Frog daemon.
143
+ *
144
+ * @returns A reference to this Dart Frog daemon, so that calls can be
145
+ * chained.
146
+ * @see {@link DartFrogDaemonEventEmitterTypes } for the types of events that
147
+ * are emitted.
148
+ */
149
+ public on (
150
+ type : DartFrogDaemonEventEmitterTypes ,
151
+ listener : ( ...args : any [ ] ) => void
152
+ ) : DartFrogDaemon {
153
+ this . daemonMessagesEventEmitter . on ( type , listener ) ;
154
+ return this ;
155
+ }
156
+
157
+ /**
158
+ * Unsubscribes a listener from events related to this Dart Frog daemon.
159
+ *
160
+ * @param type The type of event to unsubscribe from.
161
+ * @param listener The listener to unsubscribe.
162
+ * @returns A reference to this Dart Frog daemon, so that calls can be
163
+ * chained.
164
+ */
165
+ public off (
166
+ type : DartFrogDaemonEventEmitterTypes ,
167
+ listener : ( ...args : any [ ] ) => void
168
+ ) : DartFrogDaemon {
169
+ this . daemonMessagesEventEmitter . off ( type , listener ) ;
170
+ return this ;
171
+ }
18
172
}
0 commit comments