11import {
22 Emitter ,
3- IDisposable ,
43 type IPitcherClient ,
54 type protocol ,
65} from "@codesandbox/pitcher-client" ;
76
87import { Disposable } from "../../utils/disposable" ;
8+ import { DEFAULT_SHELL_SIZE } from "./terminals" ;
99
1010export type TaskDefinition = {
1111 name : string ;
@@ -17,144 +17,149 @@ export type TaskDefinition = {
1717 } ;
1818} ;
1919
20- export type Task = TaskDefinition & {
21- id : string ;
22- unconfigured ?: boolean ;
23- shellId : null | string ;
24- ports : protocol . port . Port [ ] ;
25- } ;
26-
27- type TaskShellSubscription = {
28- shellId : string ;
29- disposable : IDisposable ;
30- } ;
31-
3220export class Tasks {
3321 private disposable = new Disposable ( ) ;
34- private shellOutputListeners : Record < string , TaskShellSubscription > = { } ;
35- private onTaskOutputEmitter = this . disposable . addDisposable (
36- new Emitter < {
37- taskId : string ;
38- output : string ;
39- } > ( )
40- ) ;
41- public readonly onTaskOutput = this . onTaskOutputEmitter . event ;
42-
22+ private tasks : Task [ ] = [ ] ;
4323 constructor (
4424 sessionDisposable : Disposable ,
4525 private pitcherClient : IPitcherClient
4626 ) {
27+ this . tasks = Object . values (
28+ this . pitcherClient . clients . task . getTasks ( ) . tasks
29+ ) . map ( ( task ) => new Task ( this . pitcherClient , task ) ) ;
30+
4731 sessionDisposable . onWillDispose ( ( ) => {
4832 this . disposable . dispose ( ) ;
4933 } ) ;
50- this . disposable . addDisposable (
51- pitcherClient . clients . task . onTaskUpdate ( ( task ) =>
52- this . listenToShellOutput ( task )
53- )
54- ) ;
55- this . disposable . onWillDispose ( ( ) => {
56- Object . values ( this . shellOutputListeners ) . forEach ( ( listener ) =>
57- listener . disposable . dispose ( )
58- ) ;
59- } ) ;
60- }
61-
62- private async listenToShellOutput ( task : protocol . task . TaskDTO ) {
63- const existingListener = this . shellOutputListeners [ task . id ] ;
64-
65- // Already have shell registered
66- if ( existingListener && task . shell ?. shellId === existingListener . shellId ) {
67- return ;
68- }
69-
70- // Has removed shell
71- if (
72- existingListener &&
73- ( ! task . shell || task . shell . shellId !== existingListener . shellId )
74- ) {
75- existingListener . disposable . dispose ( ) ;
76- }
77-
78- // No new shell
79- if ( ! task . shell ) {
80- return ;
81- }
82-
83- // Has new shell
84- const taskShellId = task . shell . shellId ;
85- let listener : TaskShellSubscription = {
86- shellId : taskShellId ,
87- disposable : this . pitcherClient . clients . shell . onShellOut (
88- ( { shellId, out } ) => {
89- if ( shellId === taskShellId ) {
90- this . onTaskOutputEmitter . fire ( {
91- taskId : task . id ,
92- output : out ,
93- } ) ;
94- }
95- }
96- ) ,
97- } ;
98-
99- this . shellOutputListeners [ task . id ] = listener ;
100-
101- this . pitcherClient . clients . shell . open ( task . shell . shellId , {
102- cols : 80 ,
103- rows : 24 ,
104- } ) ;
105-
106- /*
107- this.onTaskOutputEmitter.fire({
108- taskId: task.id,
109- output: shell.buffer.join("\n"),
110- });
111- */
11234 }
11335
11436 /**
11537 * Gets all tasks that are available in the current sandbox.
11638 */
117- async getTasks ( ) : Promise < Task [ ] > {
118- const tasks = await this . pitcherClient . clients . task . getTasks ( ) ;
119-
120- return Object . values ( tasks . tasks ) . map ( taskFromDTO ) ;
39+ getTasks ( ) : Task [ ] {
40+ return this . tasks ;
12141 }
12242
12343 /**
12444 * Gets a task by its ID.
12545 */
126- async getTask ( taskId : string ) : Promise < Task | undefined > {
127- const task = await this . pitcherClient . clients . task . getTask ( taskId ) ;
128-
129- if ( ! task ) {
130- return undefined ;
131- }
46+ getTask ( taskId : string ) : Task | undefined {
47+ return this . tasks . find ( ( task ) => task . id === taskId ) ;
48+ }
49+ }
13250
133- return taskFromDTO ( task ) ;
51+ export class Task {
52+ private disposable = new Disposable ( ) ;
53+ private get shell ( ) {
54+ return this . data . shell ;
55+ }
56+ private openedShell ?: {
57+ shellId : string ;
58+ output : string [ ] ;
59+ dimensions : typeof DEFAULT_SHELL_SIZE ;
60+ } ;
61+ private onOutputEmitter = this . disposable . addDisposable (
62+ new Emitter < string > ( )
63+ ) ;
64+ public readonly onOutput = this . onOutputEmitter . event ;
65+ private onStatusChangeEmitter = this . disposable . addDisposable (
66+ new Emitter < protocol . shell . ShellProcessStatus | "IDLE" > ( )
67+ ) ;
68+ public readonly onStatusChange = this . onStatusChangeEmitter . event ;
69+ get id ( ) {
70+ return this . data . id ;
71+ }
72+ get name ( ) {
73+ return this . data . name ;
74+ }
75+ get command ( ) {
76+ return this . data . command ;
13477 }
78+ get runAtStart ( ) {
79+ return this . data . runAtStart ;
80+ }
81+ get ports ( ) {
82+ return this . data . ports ;
83+ }
84+ get status ( ) {
85+ return this . shell ?. status || "IDLE" ;
86+ }
87+ constructor (
88+ private pitcherClient : IPitcherClient ,
89+ private data : protocol . task . TaskDTO
90+ ) {
91+ pitcherClient . clients . task . onTaskUpdate ( async ( task ) => {
92+ if ( task . id !== this . id ) {
93+ return ;
94+ }
95+
96+ const lastStatus = this . status ;
97+ const lastShellId = this . shell ?. shellId ;
98+
99+ this . data = task ;
100+
101+ if ( lastStatus !== this . status ) {
102+ this . onStatusChangeEmitter . fire ( this . status ) ;
103+ }
104+
105+ if (
106+ this . openedShell &&
107+ task . shell &&
108+ task . shell . shellId !== lastShellId
109+ ) {
110+ const openedShell = await this . pitcherClient . clients . shell . open (
111+ task . shell . shellId ,
112+ this . openedShell . dimensions
113+ ) ;
114+
115+ this . openedShell = {
116+ shellId : openedShell . shellId ,
117+ output : openedShell . buffer ,
118+ dimensions : this . openedShell . dimensions ,
119+ } ;
120+
121+ this . onOutputEmitter . fire ( "\x1B[2J\x1B[3J\x1B[1;1H" ) ;
122+ openedShell . buffer . forEach ( ( out ) => this . onOutputEmitter . fire ( out ) ) ;
123+ }
124+ } ) ;
135125
136- /**
137- * Runs a task by its ID.
138- */
139- async runTask ( taskId : string ) : Promise < Task > {
140- const task = await this . pitcherClient . clients . task . runTask ( taskId ) ;
126+ pitcherClient . clients . shell . onShellOut ( ( { shellId, out } ) => {
127+ if ( ! this . shell || this . shell . shellId !== shellId || ! this . openedShell ) {
128+ return ;
129+ }
141130
142- return taskFromDTO ( task ) ;
131+ // Update output for shell
132+ this . openedShell . output . push ( out ) ;
133+ this . onOutputEmitter . fire ( out ) ;
134+ } ) ;
143135 }
136+ async open ( dimensions = DEFAULT_SHELL_SIZE ) {
137+ if ( ! this . shell ) {
138+ throw new Error ( "Task is not running" ) ;
139+ }
144140
145- async stopTask ( taskId : string ) {
146- await this . pitcherClient . clients . task . stopTask ( taskId ) ;
147- }
148- }
141+ const openedShell = await this . pitcherClient . clients . shell . open (
142+ this . shell . shellId ,
143+ dimensions
144+ ) ;
149145
150- function taskFromDTO ( value : protocol . task . TaskDTO ) : Task {
151- return {
152- id : value . id ,
153- name : value . name ,
154- command : value . command ,
155- runAtStart : value . runAtStart ,
156- preview : value . preview ,
157- shellId : value . shell ?. shellId ?? null ,
158- ports : value . ports ,
159- } ;
146+ this . openedShell = {
147+ shellId : openedShell . shellId ,
148+ output : openedShell . buffer ,
149+ dimensions,
150+ } ;
151+
152+ return this . openedShell . output . join ( "\n" ) ;
153+ }
154+ async run ( ) {
155+ await this . pitcherClient . clients . task . runTask ( this . id ) ;
156+ }
157+ async restart ( ) {
158+ await this . run ( ) ;
159+ }
160+ async stop ( ) {
161+ if ( this . shell ) {
162+ await this . pitcherClient . clients . task . stopTask ( this . id ) ;
163+ }
164+ }
160165}
0 commit comments