@@ -72,9 +72,8 @@ export class Remote {
72
72
}
73
73
74
74
// Find the workspace from the URI scheme provided!
75
- let workspace : Workspace
76
75
try {
77
- workspace = await getWorkspaceByOwnerAndName ( parts [ 0 ] , parts [ 1 ] )
76
+ this . storage . workspace = await getWorkspaceByOwnerAndName ( parts [ 0 ] , parts [ 1 ] )
78
77
} catch ( error ) {
79
78
if ( ! axios . isAxiosError ( error ) ) {
80
79
throw error
@@ -120,10 +119,10 @@ export class Remote {
120
119
121
120
const disposables : vscode . Disposable [ ] = [ ]
122
121
// Register before connection so the label still displays!
123
- disposables . push ( this . registerLabelFormatter ( `${ workspace . owner_name } /${ workspace . name } ` ) )
122
+ disposables . push ( this . registerLabelFormatter ( `${ this . storage . workspace . owner_name } /${ this . storage . workspace . name } ` ) )
124
123
125
124
let buildComplete : undefined | ( ( ) => void )
126
- if ( workspace . latest_build . status === "stopped" ) {
125
+ if ( this . storage . workspace . latest_build . status === "stopped" ) {
127
126
this . vscodeProposed . window . withProgress (
128
127
{
129
128
location : vscode . ProgressLocation . Notification ,
@@ -135,15 +134,18 @@ export class Remote {
135
134
buildComplete = r
136
135
} ) ,
137
136
)
138
- workspace = {
139
- ...workspace ,
140
- latest_build : await startWorkspace ( workspace . id ) ,
137
+ this . storage . workspace = {
138
+ ...this . storage . workspace ,
139
+ latest_build : await startWorkspace ( this . storage . workspace . id ) ,
141
140
}
142
141
}
143
142
144
143
// If a build is running we should stream the logs to the user so they can
145
144
// watch what's going on!
146
- if ( workspace . latest_build . status === "pending" || workspace . latest_build . status === "starting" ) {
145
+ if (
146
+ this . storage . workspace . latest_build . status === "pending" ||
147
+ this . storage . workspace . latest_build . status === "starting"
148
+ ) {
147
149
const writeEmitter = new vscode . EventEmitter < string > ( )
148
150
// We use a terminal instead of an output channel because it feels more
149
151
// familiar to a user!
@@ -160,11 +162,11 @@ export class Remote {
160
162
} as Partial < vscode . Pseudoterminal > as any ,
161
163
} )
162
164
// This fetches the initial bunch of logs.
163
- const logs = await getWorkspaceBuildLogs ( workspace . latest_build . id , new Date ( ) )
165
+ const logs = await getWorkspaceBuildLogs ( this . storage . workspace . latest_build . id , new Date ( ) )
164
166
logs . forEach ( ( log ) => writeEmitter . fire ( log . output + "\r\n" ) )
165
167
terminal . show ( true )
166
168
// This follows the logs for new activity!
167
- let path = `/api/v2/workspacebuilds/${ workspace . latest_build . id } /logs?follow=true`
169
+ let path = `/api/v2/workspacebuilds/${ this . storage . workspace . latest_build . id } /logs?follow=true`
168
170
if ( logs . length ) {
169
171
path += `&after=${ logs [ logs . length - 1 ] . id } `
170
172
}
@@ -198,15 +200,15 @@ export class Remote {
198
200
} )
199
201
} )
200
202
writeEmitter . fire ( "Build complete" )
201
- workspace = await getWorkspace ( workspace . id )
202
- terminal . hide ( )
203
+ this . storage . workspace = await getWorkspace ( this . storage . workspace . id )
204
+ terminal . dispose ( )
203
205
204
206
if ( buildComplete ) {
205
207
buildComplete ( )
206
208
}
207
209
}
208
210
209
- const agents = workspace . latest_build . resources . reduce ( ( acc , resource ) => {
211
+ const agents = this . storage . workspace . latest_build . resources . reduce ( ( acc , resource ) => {
210
212
return acc . concat ( resource . agents || [ ] )
211
213
} , [ ] as WorkspaceAgent [ ] )
212
214
@@ -250,7 +252,7 @@ export class Remote {
250
252
await fs . writeFile ( this . storage . getUserSettingsPath ( ) , jsonc . applyEdits ( settingsContent , edits ) )
251
253
252
254
const workspaceUpdate = new vscode . EventEmitter < Workspace > ( )
253
- const watchURL = new URL ( `${ this . storage . getURL ( ) } /api/v2/workspaces/${ workspace . id } /watch` )
255
+ const watchURL = new URL ( `${ this . storage . getURL ( ) } /api/v2/workspaces/${ this . storage . workspace . id } /watch` )
254
256
const eventSource = new EventSource ( watchURL . toString ( ) , {
255
257
headers : {
256
258
"Coder-Session-Token" : await this . storage . getSessionToken ( ) ,
@@ -262,11 +264,48 @@ export class Remote {
262
264
eventSource . addEventListener ( "error" , ( ) => {
263
265
// TODO: Add debug output that we got an error here!
264
266
} )
267
+
268
+ const workspaceUpdatedStatus = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Left , 999 )
269
+ disposables . push ( workspaceUpdatedStatus )
270
+
271
+ let hasShownOutdatedNotification = false
272
+ const refreshWorkspaceUpdatedStatus = ( newWorkspace : Workspace ) => {
273
+ // If the newly gotten workspace was updated, then we show a notification
274
+ // to the user that they should update.
275
+ if ( newWorkspace . outdated ) {
276
+ if ( ! this . storage . workspace ?. outdated || ! hasShownOutdatedNotification ) {
277
+ hasShownOutdatedNotification = true
278
+ vscode . window
279
+ . showInformationMessage ( "A new version of your workspace is available." , "Update" )
280
+ . then ( ( action ) => {
281
+ if ( action === "Update" ) {
282
+ vscode . commands . executeCommand ( "coder.workspace.update" , newWorkspace )
283
+ }
284
+ } )
285
+ }
286
+ }
287
+ if ( ! newWorkspace . outdated ) {
288
+ vscode . commands . executeCommand ( "setContext" , "coder.workspace.updatable" , false )
289
+ workspaceUpdatedStatus . hide ( )
290
+ return
291
+ }
292
+ workspaceUpdatedStatus . name = "Coder Workspace Update"
293
+ workspaceUpdatedStatus . text = "$(fold-up) Update Workspace"
294
+ workspaceUpdatedStatus . command = "coder.workspace.update"
295
+ // Important for hiding the "Update Workspace" command.
296
+ vscode . commands . executeCommand ( "setContext" , "coder.workspace.updatable" , true )
297
+ workspaceUpdatedStatus . show ( )
298
+ }
299
+ // Show an initial status!
300
+ refreshWorkspaceUpdatedStatus ( this . storage . workspace )
301
+
265
302
eventSource . addEventListener ( "data" , ( event : MessageEvent < string > ) => {
266
303
const workspace = JSON . parse ( event . data ) as Workspace
267
304
if ( ! workspace ) {
268
305
return
269
306
}
307
+ refreshWorkspaceUpdatedStatus ( workspace )
308
+ this . storage . workspace = workspace
270
309
workspaceUpdate . fire ( workspace )
271
310
if ( workspace . latest_build . status === "stopping" || workspace . latest_build . status === "stopped" ) {
272
311
const action = this . vscodeProposed . window . showInformationMessage (
@@ -283,6 +322,13 @@ export class Remote {
283
322
}
284
323
this . reloadWindow ( )
285
324
}
325
+ // If a new build is initialized for a workspace, we automatically
326
+ // reload the window. Then the build log will appear, and startup
327
+ // will continue as expected.
328
+ if ( workspace . latest_build . status === "starting" ) {
329
+ this . reloadWindow ( )
330
+ return
331
+ }
286
332
} )
287
333
288
334
if ( agent . status === "connecting" ) {
@@ -352,7 +398,7 @@ export class Remote {
352
398
} )
353
399
354
400
// Register the label formatter again because SSH overrides it!
355
- let label = `${ workspace . owner_name } /${ workspace . name } `
401
+ let label = `${ this . storage . workspace . owner_name } /${ this . storage . workspace . name } `
356
402
if ( agents . length > 1 ) {
357
403
label += `/${ agent . name } `
358
404
}
0 commit comments