Skip to content

Commit d1d3e9c

Browse files
authored
Merge pull request #125 from beNative/codex/fix-auto-update-release-feed-parsing
Fix GitHub auto-update tag lookup
2 parents cff5d28 + 763c2cf commit d1d3e9c

1 file changed

Lines changed: 85 additions & 20 deletions

File tree

electron/main.ts

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,99 @@ pythonManager.events.on('run-log', (payload) => broadcastPythonEvent('python:run
9595
pythonManager.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.
101105
const originalGetLatestTagName = GitHubProvider.prototype.getLatestTagName;
102106
GitHubProvider.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

Comments
 (0)