11import { invokeTauriCommand } from './helpers/tauri'
2+ import { transformCallback } from './tauri'
23
34/**
45 * spawns a process
56 *
6- * @param command the name of the cmd to execute e.g. 'mkdir' or 'node'
7+ * @param program the name of the program to execute e.g. 'mkdir' or 'node'
8+ * @param sidecar whether the program is a sidecar or a system program
79 * @param [args] command args
810 * @return promise resolving to the stdout text
911 */
1012async function execute (
11- command : string ,
13+ program : string ,
14+ sidecar : boolean ,
15+ onEvent : ( event : CommandEvent ) => void ,
1216 args ?: string | string [ ]
13- ) : Promise < string > {
17+ ) : Promise < number > {
1418 if ( typeof args === 'object' ) {
1519 Object . freeze ( args )
1620 }
1721
18- return invokeTauriCommand < string > ( {
22+ return invokeTauriCommand < number > ( {
1923 __tauriModule : 'Shell' ,
2024 message : {
2125 cmd : 'execute' ,
22- command,
26+ program,
27+ sidecar,
28+ onEventFn : transformCallback ( onEvent ) ,
2329 args : typeof args === 'string' ? [ args ] : args
2430 }
2531 } )
2632}
2733
34+ interface ChildProcess {
35+ code : number | null
36+ signal : number | null
37+ stdout : string
38+ stderr : string
39+ }
40+
41+ class EventEmitter < E > {
42+ eventListeners : { [ key : string ] : Array < ( arg : any ) => void > } = { }
43+
44+ private addEventListener ( event : string , handler : ( arg : any ) => void ) : void {
45+ if ( event in this . eventListeners ) {
46+ // eslint-disable-next-line security/detect-object-injection
47+ this . eventListeners [ event ] . push ( handler )
48+ } else {
49+ // eslint-disable-next-line security/detect-object-injection
50+ this . eventListeners [ event ] = [ handler ]
51+ }
52+ }
53+
54+ _emit ( event : E , payload : any ) : void {
55+ if ( event in this . eventListeners ) {
56+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
57+ const listeners = this . eventListeners [ event as any ]
58+ for ( const listener of listeners ) {
59+ listener ( payload )
60+ }
61+ }
62+ }
63+
64+ on ( event : E , handler : ( arg : any ) => void ) : EventEmitter < E > {
65+ this . addEventListener ( event as any , handler )
66+ return this
67+ }
68+ }
69+
70+ class Child {
71+ pid : number
72+
73+ constructor ( pid : number ) {
74+ this . pid = pid
75+ }
76+
77+ async write ( data : string | number [ ] ) : Promise < void > {
78+ return invokeTauriCommand ( {
79+ __tauriModule : 'Shell' ,
80+ message : {
81+ cmd : 'stdinWrite' ,
82+ pid : this . pid ,
83+ buffer : data
84+ }
85+ } )
86+ }
87+
88+ async kill ( ) : Promise < void > {
89+ return invokeTauriCommand ( {
90+ __tauriModule : 'Shell' ,
91+ message : {
92+ cmd : 'killChild' ,
93+ pid : this . pid
94+ }
95+ } )
96+ }
97+ }
98+
99+ class Command extends EventEmitter < 'close' | 'error' > {
100+ program : string
101+ args : string [ ]
102+ sidecar = false
103+ stdout = new EventEmitter < 'data' > ( )
104+ stderr = new EventEmitter < 'data' > ( )
105+ pid : number | null = null
106+
107+ constructor ( program : string , args : string | string [ ] = [ ] ) {
108+ super ( )
109+ this . program = program
110+ this . args = typeof args === 'string' ? [ args ] : args
111+ }
112+
113+ /**
114+ * Creates a command to execute the given sidecar binary
115+ *
116+ * @param {string } program Binary name
117+ *
118+ * @return {Command }
119+ */
120+ static sidecar ( program : string , args : string | string [ ] = [ ] ) : Command {
121+ const instance = new Command ( program , args )
122+ instance . sidecar = true
123+ return instance
124+ }
125+
126+ async spawn ( ) : Promise < Child > {
127+ return execute (
128+ this . program ,
129+ this . sidecar ,
130+ ( event ) => {
131+ switch ( event . event ) {
132+ case 'Error' :
133+ this . _emit ( 'error' , event . payload )
134+ break
135+ case 'Terminated' :
136+ this . _emit ( 'close' , event . payload )
137+ break
138+ case 'Stdout' :
139+ this . stdout . _emit ( 'data' , event . payload )
140+ break
141+ case 'Stderr' :
142+ this . stderr . _emit ( 'data' , event . payload )
143+ break
144+ }
145+ } ,
146+ this . args
147+ ) . then ( ( pid ) => new Child ( pid ) )
148+ }
149+
150+ async execute ( ) : Promise < ChildProcess > {
151+ return new Promise ( ( resolve , reject ) => {
152+ this . on ( 'error' , reject )
153+ const stdout : string [ ] = [ ]
154+ const stderr : string [ ] = [ ]
155+ this . stdout . on ( 'data' , ( line ) => {
156+ stdout . push ( line )
157+ } )
158+ this . stderr . on ( 'data' , ( line ) => {
159+ stderr . push ( line )
160+ } )
161+ this . on ( 'close' , ( payload : TerminatedPayload ) => {
162+ resolve ( {
163+ code : payload . code ,
164+ signal : payload . signal ,
165+ stdout : stdout . join ( '\n' ) ,
166+ stderr : stderr . join ( '\n' )
167+ } )
168+ } )
169+ this . spawn ( ) . catch ( reject )
170+ } )
171+ }
172+ }
173+
174+ interface Event < T , V > {
175+ event : T
176+ payload : V
177+ }
178+
179+ interface TerminatedPayload {
180+ code : number | null
181+ signal : number | null
182+ }
183+
184+ type CommandEvent =
185+ | Event < 'Stdout' , string >
186+ | Event < 'Stderr' , string >
187+ | Event < 'Terminated' , TerminatedPayload >
188+ | Event < 'Error' , string >
189+
28190/**
29191 * opens a path or URL with the system's default app,
30192 * or the one specified with `openWith`
@@ -43,4 +205,4 @@ async function open(path: string, openWith?: string): Promise<void> {
43205 } )
44206}
45207
46- export { execute , open }
208+ export { Command , Child , open }
0 commit comments