feat: add downloadable release management for desktop client
Frontend CI / Frontend (push) Successful in 4m1s
Backend CI / Backend (push) Failing after 8m51s

- Extend DesktopClientReleaseCheckResponse with platform, arch, and channel fields.
- Introduce DesktopClientDownloadableRelease and DesktopClientDownloadableReleaseListResponse interfaces for managing downloadable releases.
- Implement ListDownloadable method in DesktopClientReleaseService to retrieve downloadable releases based on the specified channel.
- Update DesktopClientReleaseCheckResult to include platform, arch, and channel information.
- Add resolveInstallerDownloadURL method to handle download URL resolution for releases.
- Create new endpoint in DesktopReleaseHandler for listing downloadable releases.
- Update router to include the new downloadable releases route.
- Enhance tests to cover the new functionality for resolving download URLs and listing downloadable releases.
This commit is contained in:
2026-05-31 19:28:35 +08:00
parent de9c52be6d
commit d6866cd85b
8 changed files with 1419 additions and 201 deletions
+78
View File
@@ -35,6 +35,10 @@ import type {
CreatePublishJobRequest,
CreatePublishJobResponse,
DesktopAccountInfo,
DesktopClientDownloadableRelease,
DesktopClientDownloadableReleaseListResponse,
DesktopClientReleaseCheckResponse,
DesktopClientReleaseDownloadURLResponse,
FolderDeletePreview,
GenerateFromRuleRequest,
GenerateFromRuleResponse,
@@ -1285,6 +1289,80 @@ export const tenantAccountsApi = {
},
}
export const desktopClientApi = {
downloadableReleases(channel = 'stable') {
return apiClient.get<DesktopClientDownloadableReleaseListResponse>(
'/api/tenant/desktop-client/releases/downloadable',
{ params: { channel } },
)
},
publicLatest(params: { platform: string; arch: string; channel?: string; version?: string }) {
return publicClient.get<DesktopClientReleaseCheckResponse>('/api/desktop/releases/latest', {
params: {
platform: params.platform,
arch: params.arch,
channel: params.channel ?? 'stable',
version: params.version ?? '0.0.0',
},
})
},
publicDownloadURL(params: {
platform: string
arch: string
channel?: string
version?: string
}) {
return publicClient.get<DesktopClientReleaseDownloadURLResponse>(
'/api/desktop/releases/download-url',
{
params: {
platform: params.platform,
arch: params.arch,
channel: params.channel ?? 'stable',
version: params.version ?? '0.0.0',
},
},
)
},
async publicFallbackDownloadableReleases(): Promise<DesktopClientDownloadableReleaseListResponse> {
const targets = [
{ platform: 'win32', arch: 'x64' },
{ platform: 'darwin', arch: 'arm64' },
]
const results = await Promise.allSettled(
targets.map((target) => this.publicLatest({ ...target, channel: 'stable' })),
)
return {
items: results.flatMap((result, index) => {
if (result.status !== 'fulfilled') {
return []
}
const release = result.value
const target = targets[index]
const item: DesktopClientDownloadableRelease = {
id: index + 1,
platform: release.platform ?? target.platform,
arch: release.arch ?? target.arch,
channel: release.channel ?? 'stable',
version: release.latest_version,
download_url: release.download_url ?? null,
oss_object_key: release.oss_object_key ?? null,
file_name: release.file_name ?? null,
file_size_bytes: release.file_size_bytes ?? null,
sha256: release.sha256 ?? null,
release_notes: release.release_notes ?? null,
published_at: release.published_at ?? null,
updated_at: release.published_at ?? new Date(0).toISOString(),
force_update: release.force_update,
min_supported_version: release.min_supported_version,
download_source: release.download_source,
}
return [item]
}),
}
},
}
export const publishJobsApi = {
create(payload: CreatePublishJobRequest) {
return apiClient.post<CreatePublishJobResponse, CreatePublishJobRequest>(