@@ -59,6 +59,7 @@ export type UpdateState = {
5959 notifiedVersion ?: string
6060 skippedVersion ?: string
6161 completedVersion ?: string
62+ failedInstallVersion ?: string
6263}
6364
6465export type UpdateTelemetryTrack = (
@@ -129,16 +130,31 @@ function isVerifiedUpgrade(currentVersion: string, previousVersion: string | und
129130 && gt ( currentVersion , previousVersion )
130131}
131132
132- function reconcileStartupReceipt ( value : UpdateSettings , currentVersion : string ) : UpdateSettings {
133+ /**
134+ * A silent installer reports nothing back: the app quits, the installer runs
135+ * hidden, and the only evidence either way is the version that comes back up.
136+ * A pending receipt that does not match this launch therefore means the install
137+ * did not take effect, and it has to become a visible error rather than silence.
138+ */
139+ function reconcileStartupReceipt (
140+ value : UpdateSettings ,
141+ currentVersion : string ,
142+ ) : { settings : UpdateSettings , failedInstallVersion ?: string } {
133143 let completedVersion = value . completedVersion === currentVersion ? value . completedVersion : undefined
144+ let failedInstallVersion : string | undefined
134145 if ( value . pendingInstallVersion === currentVersion ) {
135146 if ( isVerifiedUpgrade ( currentVersion , value . lastRunVersion ) ) completedVersion = currentVersion
147+ } else if ( value . pendingInstallVersion !== undefined ) {
148+ failedInstallVersion = value . pendingInstallVersion
136149 }
137150 return {
138- ...value ,
139- pendingInstallVersion : undefined ,
140- completedVersion,
141- lastRunVersion : currentVersion ,
151+ settings : {
152+ ...value ,
153+ pendingInstallVersion : undefined ,
154+ completedVersion,
155+ lastRunVersion : currentVersion ,
156+ } ,
157+ failedInstallVersion,
142158 }
143159}
144160
@@ -295,7 +311,7 @@ function scheduleChecks(): void {
295311async function runCheck ( ) : Promise < UpdateState > {
296312 if ( checkPromise !== undefined ) return checkPromise
297313 checkPromise = ( async ( ) => {
298- updateState ( { status : 'checking' , message : undefined } )
314+ updateState ( { status : 'checking' , message : undefined , failedInstallVersion : undefined } )
299315 try {
300316 configureExplicitConsent ( )
301317 await autoUpdater . checkForUpdates ( )
@@ -329,7 +345,7 @@ function wireUpdaterEvents(): void {
329345 if ( listenersWired ) return
330346 try {
331347 autoUpdater . on ( 'checking-for-update' , ( ) => {
332- updateState ( { status : 'checking' , message : undefined } )
348+ updateState ( { status : 'checking' , message : undefined , failedInstallVersion : undefined } )
333349 } )
334350 autoUpdater . on ( 'update-available' , applyAvailableUpdate )
335351 autoUpdater . on ( 'update-not-available' , ( ) => {
@@ -389,12 +405,19 @@ export function initUpdater(
389405 updateTelemetryTrack = track
390406 const userData = app . getPath ( 'userData' )
391407 settings = readUpdateSettings ( userData )
408+ let failedInstallVersion : string | undefined
392409 if ( app . isPackaged ) {
393- settings = reconcileStartupReceipt ( settings , app . getVersion ( ) )
410+ const receipt = reconcileStartupReceipt ( settings , app . getVersion ( ) )
411+ settings = receipt . settings
412+ failedInstallVersion = receipt . failedInstallVersion
394413 writeUpdateSettings ( userData , settings )
395414 }
396415 state = {
397- status : app . isPackaged ? 'idle' : 'disabled' ,
416+ status : app . isPackaged ? ( failedInstallVersion === undefined ? 'idle' : 'error' ) : 'disabled' ,
417+ message : failedInstallVersion === undefined
418+ ? undefined
419+ : `Update to v${ failedInstallVersion } did not complete. Pythinker is still on v${ app . getVersion ( ) } .` ,
420+ failedInstallVersion,
398421 installedVersion : app . getVersion ( ) ,
399422 autoUpdate : settings . autoUpdate ,
400423 channel : settings . channel ,
@@ -619,7 +642,10 @@ export function installDownloadedUpdateNow(): UpdateState {
619642 } catch {
620643 // Telemetry must never delay an explicit installation.
621644 }
622- autoUpdater . quitAndInstall ( )
645+ // Silent NSIS install: `--updated /S --force-run`. Without `isSilent` the
646+ // assisted installer opens its wizard, and relaunch falls to
647+ // `autoRunAppAfterInstall` instead of `isForceRunAfter`.
648+ autoUpdater . quitAndInstall ( true , true )
623649 } catch ( error ) {
624650 installRequestedVersion = undefined
625651 if ( settings . pendingInstallVersion === version ) {
0 commit comments