@@ -15,9 +15,17 @@ import { z } from 'zod';
1515
1616import { getTuiConfigPath , parseTuiConfig } from '#/tui/config' ;
1717import { readUpdateCache } from '#/cli/update/cache' ;
18- import { isAutoUpdateDisabledByEnv , shouldAutoInstallUpdates } from '#/cli/update/preflight' ;
18+ import { readUpdateInstallState } from '#/cli/update/install-state' ;
19+ import {
20+ automaticUpdateModeFor ,
21+ isAutoUpdateDisabledByEnv ,
22+ shouldAutoInstallUpdates ,
23+ type AutomaticUpdateMode ,
24+ } from '#/cli/update/preflight' ;
1925import { detectInstallSource } from '#/cli/update/source' ;
26+ import type { UpdateInstallFailure } from '#/cli/update/types' ;
2027import { getHostPackageRoot , getVersion } from '#/cli/version' ;
28+ import { getUpdateInstallLogFile } from '#/utils/paths' ;
2129
2230interface WritableLike {
2331 write ( chunk : string ) : boolean ;
@@ -50,6 +58,12 @@ export interface DoctorRuntimeInfo {
5058 readonly latest : string | null ;
5159 readonly checkedAt : string | null ;
5260 readonly autoUpdate ?: 'on' | 'off' | 'env-disabled' ;
61+ readonly mode ?: AutomaticUpdateMode ;
62+ readonly pendingVersion ?: string ;
63+ readonly pendingRequestedBy ?: 'automatic' | 'manual' ;
64+ readonly activeOperation ?: string ;
65+ readonly lastFailure ?: string ;
66+ readonly logPath ?: string ;
5367 } ;
5468}
5569
@@ -166,11 +180,12 @@ function resolveDeps(deps: Partial<DoctorDeps> | DoctorDeps | undefined): Resolv
166180 runtimeInfo :
167181 deps ?. runtimeInfo ??
168182 ( async ( ) => {
169- const [ installSource , installations , ripgrep , update , autoInstall ] = await Promise . all ( [
183+ const [ installSource , installations , ripgrep , update , installState , autoInstall ] = await Promise . all ( [
170184 detectInstallSource ( ) ,
171185 findPythinkerExecutables ( ) ,
172186 findExistingRg ( resolvePythinkerHome ( ) ) ,
173187 readUpdateCache ( ) ,
188+ readUpdateInstallState ( ) ,
174189 shouldAutoInstallUpdates ( ) ,
175190 ] ) ;
176191 return {
@@ -184,12 +199,31 @@ function resolveDeps(deps: Partial<DoctorDeps> | DoctorDeps | undefined): Resolv
184199 latest : update . latest ,
185200 checkedAt : update . checkedAt ,
186201 autoUpdate : isAutoUpdateDisabledByEnv ( ) ? 'env-disabled' : autoInstall ? 'on' : 'off' ,
202+ mode : automaticUpdateModeFor ( installSource , process . platform ) ,
203+ pendingVersion : installState . pending ?. version ,
204+ pendingRequestedBy : installState . pending ?. requestedBy ,
205+ activeOperation :
206+ installState . active === null
207+ ? undefined
208+ : `${ installState . active . operation ?? 'install' } ${ installState . active . version } ` ,
209+ lastFailure :
210+ installState . lastFailure === null
211+ ? undefined
212+ : formatUpdateFailure ( installState . lastFailure ) ,
213+ logPath : getUpdateInstallLogFile ( ) ,
187214 } ,
188215 } ;
189216 } ) ,
190217 } ;
191218}
192219
220+ function formatUpdateFailure ( failure : UpdateInstallFailure ) : string {
221+ const summary = `${ failure . operation ?? 'install' } ${ failure . version } ` +
222+ `(attempt ${ String ( failure . attempts ) } )` ;
223+ const message = failure . message ?. replaceAll ( / \s + / gu, ' ' ) . trim ( ) ;
224+ return message === undefined || message === '' ? summary : `${ summary } : ${ message } ` ;
225+ }
226+
193227export async function findPythinkerExecutables (
194228 pathValue = process . env [ 'PATH' ] ,
195229 platform : NodeJS . Platform = process . platform ,
@@ -368,25 +402,61 @@ function formatRuntimeInfo(info: DoctorRuntimeInfo | undefined): string[] {
368402 ? [ ]
369403 : [
370404 ' Update channel: CDN staged rollout' ,
371- ...( info . update . autoUpdate === undefined
372- ? [ ]
373- : [
374- info . update . autoUpdate === 'env-disabled'
375- ? ' Auto-update: disabled by PYTHINKER_CODE_NO_AUTO_UPDATE'
376- : ` Auto-update: ${ info . update . autoUpdate } (tui.toml [upgrade].auto_install)` ,
377- ] ) ,
405+ ...formatAutomaticUpdate ( info ) ,
378406 ...( info . update . latest === null
379407 ? [ ' Latest cached version: unavailable' ]
380408 : [
381409 ` Latest cached version: ${ info . update . latest } ${
382410 info . update . checkedAt === null ? '' : ` (checked ${ info . update . checkedAt } )`
383411 } `,
384412 ] ) ,
413+ ...formatPreparedUpdate ( info . update ) ,
414+ ...( info . update . activeOperation === undefined
415+ ? [ ]
416+ : [ ` Update operation: ${ info . update . activeOperation } ` ] ) ,
417+ ...( info . update . lastFailure === undefined
418+ ? [ ]
419+ : [ ` Last update failure: ${ info . update . lastFailure } ` ] ) ,
420+ ...( info . update . logPath === undefined ? [ ] : [ ` Update log: ${ info . update . logPath } ` ] ) ,
385421 ] ) ,
386422 '' ,
387423 ] ;
388424}
389425
426+ function formatPreparedUpdate (
427+ update : NonNullable < DoctorRuntimeInfo [ 'update' ] > ,
428+ ) : string [ ] {
429+ if ( update . pendingVersion === undefined ) return [ ] ;
430+ if ( update . pendingRequestedBy === 'automatic' && update . autoUpdate !== 'on' ) {
431+ return [
432+ ` Prepared update: ${ update . pendingVersion } ` +
433+ '(automatic activation paused until auto-update is enabled)' ,
434+ ] ;
435+ }
436+ return [ ` Prepared update: ${ update . pendingVersion } (installs on next launch)` ] ;
437+ }
438+
439+ function formatAutomaticUpdate ( info : DoctorRuntimeInfo ) : string [ ] {
440+ const update = info . update ;
441+ if ( update ?. autoUpdate === undefined ) return [ ] ;
442+ if ( update . autoUpdate === 'env-disabled' ) {
443+ return [ ' Auto-update: disabled by PYTHINKER_CODE_NO_AUTO_UPDATE' ] ;
444+ }
445+ if ( update . autoUpdate === 'off' ) {
446+ return [ ' Auto-update: off (tui.toml [upgrade].auto_install)' ] ;
447+ }
448+ switch ( update . mode ) {
449+ case 'restart-install' :
450+ return [ ' Auto-update: on (prepare in background; install on next launch)' ] ;
451+ case 'background-install' :
452+ return [ ' Auto-update: on (installs in background)' ] ;
453+ case 'manual' :
454+ return [ ` Auto-update: unavailable for ${ info . installSource } ` ] ;
455+ case undefined :
456+ return [ ' Auto-update: on (tui.toml [upgrade].auto_install)' ] ;
457+ }
458+ }
459+
390460function formatResults ( results : readonly CheckResult [ ] ) : string [ ] {
391461 const lines : string [ ] = [ ] ;
392462 for ( const result of results ) {
0 commit comments