@@ -95,34 +95,99 @@ pythonManager.events.on('run-log', (payload) => broadcastPythonEvent('python:run
9595pythonManager . events . on ( 'run-status' , ( payload ) => broadcastPythonEvent ( 'python:run-status' , payload ) ) ;
9696
9797// Work around GitHub returning HTTP 406 for JSON-only requests to the
98- // `/releases/latest` endpoint by attempting to resolve the latest tag via the
99- // REST API before falling back to the default behaviour implemented by
100- // electron-updater. This avoids the auto-update check failing on startup.
98+ // `/releases/latest` endpoint by resolving the latest tag via the REST API.
99+ // We attempt to use the official "latest" endpoint first and then fall back to
100+ // listing the most recent releases if that endpoint reports that no production
101+ // release exists (HTTP 404). This avoids the auto-update check failing on
102+ // startup while still respecting the expectation that only published,
103+ // non-prerelease builds are considered when automatic updates are disabled for
104+ // prerelease channels.
101105const originalGetLatestTagName = GitHubProvider . prototype . getLatestTagName ;
102106GitHubProvider . prototype . getLatestTagName = async function ( this : GitHubProvider , cancellationToken ) {
103- const { owner, repo } = this . options ;
104- const apiUrl = new URL ( `/repos/${ owner } /${ repo } /releases/latest` , 'https://api.github.com' ) ;
107+ const { owner, repo, host } = this . options ;
108+ const apiHost = ! host || host === 'github.com' ? 'https://api.github.com' : `https://${ host } ` ;
109+ const apiPathPrefix = host && ! [ 'github.com' , 'api.github.com' ] . includes ( host ) ? '/api/v3' : '' ;
110+
111+ const buildApiUrl = ( suffix : string ) => {
112+ const normalizedSuffix = suffix . startsWith ( '/' ) ? suffix : `/${ suffix } ` ;
113+ return new URL ( `${ apiPathPrefix } ${ normalizedSuffix } ` , apiHost ) ;
114+ } ;
115+
116+ const requestHeaders = {
117+ Accept : 'application/vnd.github+json' ,
118+ 'User-Agent' : 'docforge-auto-updater' ,
119+ 'X-GitHub-Api-Version' : '2022-11-28' ,
120+ } as const ;
121+
122+ const tryResolveFromLatestEndpoint = async ( ) : Promise < string | null > => {
123+ try {
124+ const rawResponse = await this . httpRequest (
125+ buildApiUrl ( `/repos/${ owner } /${ repo } /releases/latest` ) ,
126+ requestHeaders ,
127+ cancellationToken
128+ ) ;
129+
130+ if ( ! rawResponse ) {
131+ return null ;
132+ }
105133
106- try {
107- const rawResponse = await this . httpRequest (
108- apiUrl ,
109- {
110- Accept : 'application/vnd.github+json' ,
111- 'User-Agent' : 'docforge-auto-updater'
112- } ,
113- cancellationToken
114- ) ;
134+ const parsed = JSON . parse ( rawResponse ) as { tag_name ?: string | null } | null ;
135+ return parsed ?. tag_name ?? null ;
136+ } catch ( error : unknown ) {
137+ const statusCode = typeof error === 'object' && error !== null && 'statusCode' in error
138+ ? ( error as { statusCode ?: number } ) . statusCode
139+ : undefined ;
115140
116- if ( rawResponse ) {
117- const parsed = JSON . parse ( rawResponse ) as { tag_name ?: string } ;
118- if ( parsed . tag_name ) {
119- return parsed . tag_name ;
141+ if ( statusCode !== 404 ) {
142+ console . warn ( 'Failed to query GitHub latest release endpoint for auto-update.' , error ) ;
120143 }
144+
145+ return null ;
121146 }
122- } catch ( error ) {
123- console . warn ( 'Failed to resolve latest release tag via GitHub API, falling back to default behaviour.' , error ) ;
147+ } ;
148+
149+ const tryResolveFromReleaseList = async ( ) : Promise < string | null > => {
150+ try {
151+ const rawResponse = await this . httpRequest (
152+ buildApiUrl ( `/repos/${ owner } /${ repo } /releases?per_page=15` ) ,
153+ requestHeaders ,
154+ cancellationToken
155+ ) ;
156+
157+ if ( ! rawResponse ) {
158+ return null ;
159+ }
160+
161+ const releases = JSON . parse ( rawResponse ) as Array < {
162+ tag_name ?: string | null ;
163+ draft ?: boolean | null ;
164+ prerelease ?: boolean | null ;
165+ } > ;
166+
167+ for ( const release of releases ) {
168+ if ( release ?. tag_name && ! release ?. draft && ! release ?. prerelease ) {
169+ return release . tag_name ;
170+ }
171+ }
172+
173+ return null ;
174+ } catch ( error ) {
175+ console . warn ( 'Failed to query GitHub releases list for auto-update fallback.' , error ) ;
176+ return null ;
177+ }
178+ } ;
179+
180+ const latestTag = await tryResolveFromLatestEndpoint ( ) ;
181+ if ( latestTag ) {
182+ return latestTag ;
183+ }
184+
185+ const fallbackTag = await tryResolveFromReleaseList ( ) ;
186+ if ( fallbackTag ) {
187+ return fallbackTag ;
124188 }
125189
190+ console . warn ( 'Falling back to the default electron-updater GitHub provider behaviour for tag resolution.' ) ;
126191 return originalGetLatestTagName . call ( this , cancellationToken ) ;
127192} ;
128193
0 commit comments