@@ -17,6 +17,7 @@ import { IProductService } from '../../product/common/productService.js';
1717import { asJson , IRequestService } from '../../request/common/request.js' ;
1818import { IApplicationStorageMainService } from '../../storage/electron-main/storageMainService.js' ;
1919import { ITelemetryService } from '../../telemetry/common/telemetry.js' ;
20+ import { StorageScope , StorageTarget } from '../../storage/common/storage.js' ;
2021import { AvailableForDownload , IUpdate , State , StateType , UpdateType } from '../common/update.js' ;
2122import { IMeteredConnectionService } from '../../meteredConnection/common/meteredConnection.js' ;
2223import { AbstractUpdateService , IUpdateURLOptions } from './abstractUpdateService.js' ;
@@ -32,6 +33,11 @@ import { Promises } from '../../../base/node/pfs.js';
3233 * 3. Extracts the .app bundle → Ready
3334 * 4. On restart: spawns a helper script that swaps the .app and relaunches
3435 */
36+ const STAGED_UPDATE_PATH_KEY = 'workstreamsUpdate/stagedPath' ;
37+ const STAGED_UPDATE_COMMIT_KEY = 'workstreamsUpdate/stagedCommit' ;
38+ const STAGED_UPDATE_VERSION_KEY = 'workstreamsUpdate/stagedProductVersion' ;
39+ const STAGED_UPDATE_CHANGELOG_KEY = 'workstreamsUpdate/stagedChangelogUrl' ;
40+
3541export class WorkstreamsUpdateService extends AbstractUpdateService implements IRelaunchHandler {
3642
3743 private stagedUpdatePath : string | undefined ;
@@ -66,6 +72,50 @@ export class WorkstreamsUpdateService extends AbstractUpdateService implements I
6672 return true ;
6773 }
6874
75+ protected override async postInitialize ( ) : Promise < void > {
76+ const stagedPath = this . applicationStorageMainService . get ( STAGED_UPDATE_PATH_KEY , StorageScope . APPLICATION ) ;
77+ const stagedCommit = this . applicationStorageMainService . get ( STAGED_UPDATE_COMMIT_KEY , StorageScope . APPLICATION ) ;
78+ const stagedVersion = this . applicationStorageMainService . get ( STAGED_UPDATE_VERSION_KEY , StorageScope . APPLICATION ) ;
79+ const stagedChangelog = this . applicationStorageMainService . get ( STAGED_UPDATE_CHANGELOG_KEY , StorageScope . APPLICATION ) ;
80+
81+ if ( stagedPath && stagedCommit && stagedCommit !== this . productService . commit ) {
82+ try {
83+ const stat = await fs . promises . stat ( stagedPath ) ;
84+ if ( stat . isDirectory ( ) ) {
85+ this . logService . info ( 'workstreams-update#postInitialize - restoring staged update' , { stagedPath, stagedCommit } ) ;
86+ this . stagedUpdatePath = stagedPath ;
87+ this . setState ( State . Ready ( {
88+ version : stagedCommit ,
89+ productVersion : stagedVersion || '' ,
90+ changelogUrl : stagedChangelog || undefined ,
91+ } , true , false ) ) ;
92+ return ;
93+ }
94+ } catch {
95+ // File doesn't exist — clean up stale storage
96+ }
97+ }
98+
99+ // Clean up stale keys if no valid staged update
100+ this . clearStagedUpdateStorage ( ) ;
101+ }
102+
103+ private clearStagedUpdateStorage ( ) : void {
104+ this . applicationStorageMainService . remove ( STAGED_UPDATE_PATH_KEY , StorageScope . APPLICATION ) ;
105+ this . applicationStorageMainService . remove ( STAGED_UPDATE_COMMIT_KEY , StorageScope . APPLICATION ) ;
106+ this . applicationStorageMainService . remove ( STAGED_UPDATE_VERSION_KEY , StorageScope . APPLICATION ) ;
107+ this . applicationStorageMainService . remove ( STAGED_UPDATE_CHANGELOG_KEY , StorageScope . APPLICATION ) ;
108+ }
109+
110+ private persistStagedUpdate ( extractedAppPath : string , update : IUpdate ) : void {
111+ this . applicationStorageMainService . store ( STAGED_UPDATE_PATH_KEY , extractedAppPath , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
112+ this . applicationStorageMainService . store ( STAGED_UPDATE_COMMIT_KEY , update . version ! , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
113+ this . applicationStorageMainService . store ( STAGED_UPDATE_VERSION_KEY , update . productVersion ! , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
114+ if ( update . changelogUrl ) {
115+ this . applicationStorageMainService . store ( STAGED_UPDATE_CHANGELOG_KEY , update . changelogUrl , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
116+ }
117+ }
118+
69119 protected buildUpdateFeedUrl ( quality : string , _commit : string , _options ?: IUpdateURLOptions ) : string | undefined {
70120 const baseUrl = this . productService . updateUrl ;
71121 if ( ! baseUrl ) {
@@ -166,6 +216,15 @@ export class WorkstreamsUpdateService extends AbstractUpdateService implements I
166216 return ;
167217 }
168218
219+ // Already staged this exact version — skip re-download
220+ if ( this . stagedUpdatePath && this . state . type === StateType . Ready ) {
221+ const stagedCommit = this . applicationStorageMainService . get ( STAGED_UPDATE_COMMIT_KEY , StorageScope . APPLICATION ) ;
222+ if ( stagedCommit === manifest . version ) {
223+ this . logService . info ( 'workstreams-update#checkGitHubRelease - already staged' , manifest . version ) ;
224+ return ;
225+ }
226+ }
227+
169228 this . logService . info ( 'workstreams-update#checkGitHubRelease - update available' , {
170229 current : this . productService . commit ,
171230 new : manifest . version ,
@@ -182,6 +241,9 @@ export class WorkstreamsUpdateService extends AbstractUpdateService implements I
182241 } ;
183242
184243 this . setState ( State . AvailableForDownload ( update ) ) ;
244+
245+ // Auto-download in background — no user interaction needed
246+ this . doDownloadUpdate ( State . AvailableForDownload ( update ) as AvailableForDownload ) ;
185247 } catch ( err ) {
186248 this . logService . error ( 'workstreams-update#checkGitHubRelease - error' , err ) ;
187249 this . setState ( State . Idle ( UpdateType . Archive , explicit ? String ( err ) : undefined ) ) ;
@@ -244,6 +306,7 @@ export class WorkstreamsUpdateService extends AbstractUpdateService implements I
244306 }
245307
246308 this . stagedUpdatePath = extractedAppPath ;
309+ this . persistStagedUpdate ( extractedAppPath , update ) ;
247310
248311 // Clean up the zip
249312 await Promises . rm ( zipPath ) . catch ( ( ) => { /* ignore */ } ) ;
@@ -277,6 +340,8 @@ export class WorkstreamsUpdateService extends AbstractUpdateService implements I
277340 return ;
278341 }
279342
343+ this . clearStagedUpdateStorage ( ) ;
344+
280345 const currentAppPath = this . resolveCurrentAppPath ( ) ;
281346 if ( ! currentAppPath ) {
282347 this . logService . error ( 'workstreams-update#doQuitAndInstall - could not resolve current app path' ) ;
0 commit comments