Skip to content

Commit d9cedf8

Browse files
committed
fix(electron): app updater (#8043)
1 parent a802dc4 commit d9cedf8

File tree

22 files changed

+537
-730
lines changed

22 files changed

+537
-730
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// credits: migrated from https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/providers/GitHubProvider.ts
2+
3+
import type { CustomPublishOptions } from 'builder-util-runtime';
4+
import { newError } from 'builder-util-runtime';
5+
import type {
6+
AppUpdater,
7+
ResolvedUpdateFileInfo,
8+
UpdateFileInfo,
9+
UpdateInfo,
10+
} from 'electron-updater';
11+
import { CancellationToken, Provider } from 'electron-updater';
12+
import type { ProviderRuntimeOptions } from 'electron-updater/out/providers/Provider';
13+
import {
14+
getFileList,
15+
parseUpdateInfo,
16+
} from 'electron-updater/out/providers/Provider';
17+
18+
import type { buildType } from '../config';
19+
import { isSquirrelBuild } from './utils';
20+
21+
interface GithubUpdateInfo extends UpdateInfo {
22+
tag: string;
23+
}
24+
25+
interface GithubRelease {
26+
name: string;
27+
tag_name: string;
28+
published_at: string;
29+
assets: Array<{
30+
name: string;
31+
url: string;
32+
}>;
33+
}
34+
35+
interface UpdateProviderOptions {
36+
feedUrl?: string;
37+
channel: typeof buildType;
38+
}
39+
40+
export class AFFiNEUpdateProvider extends Provider<GithubUpdateInfo> {
41+
static configFeed(options: UpdateProviderOptions): CustomPublishOptions {
42+
return {
43+
provider: 'custom',
44+
feedUrl: 'https://affine.pro/api/worker/releases',
45+
updateProvider: AFFiNEUpdateProvider,
46+
...options,
47+
};
48+
}
49+
50+
constructor(
51+
private readonly options: CustomPublishOptions,
52+
_updater: AppUpdater,
53+
runtimeOptions: ProviderRuntimeOptions
54+
) {
55+
super(runtimeOptions);
56+
}
57+
58+
get feedUrl(): URL {
59+
const url = new URL(this.options.feedUrl);
60+
url.searchParams.set('channel', this.options.channel);
61+
url.searchParams.set('minimal', 'true');
62+
63+
return url;
64+
}
65+
66+
async getLatestVersion(): Promise<GithubUpdateInfo> {
67+
const cancellationToken = new CancellationToken();
68+
69+
const releasesJsonStr = await this.httpRequest(
70+
this.feedUrl,
71+
{
72+
accept: 'application/json',
73+
'cache-control': 'no-cache',
74+
},
75+
cancellationToken
76+
);
77+
78+
if (!releasesJsonStr) {
79+
throw new Error(
80+
`Failed to get releases from ${this.feedUrl.toString()}, response is empty`
81+
);
82+
}
83+
84+
const releases = JSON.parse(releasesJsonStr);
85+
86+
if (releases.length === 0) {
87+
throw new Error(
88+
`No published versions in channel ${this.options.channel}`
89+
);
90+
}
91+
92+
const latestRelease = releases[0] as GithubRelease;
93+
const tag = latestRelease.tag_name;
94+
95+
const channelFileName = getChannelFilename(this.getDefaultChannelName());
96+
const channelFileAsset = latestRelease.assets.find(({ url }) =>
97+
url.endsWith(channelFileName)
98+
);
99+
100+
if (!channelFileAsset) {
101+
throw newError(
102+
`Cannot find ${channelFileName} in the latest release artifacts.`,
103+
'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND'
104+
);
105+
}
106+
107+
const channelFileUrl = new URL(channelFileAsset.url);
108+
const channelFileContent = await this.httpRequest(channelFileUrl);
109+
110+
const result = parseUpdateInfo(
111+
channelFileContent,
112+
channelFileName,
113+
channelFileUrl
114+
);
115+
116+
const files: UpdateFileInfo[] = [];
117+
118+
result.files.forEach(file => {
119+
const asset = latestRelease.assets.find(({ name }) => name === file.url);
120+
if (asset) {
121+
file.url = asset.url;
122+
}
123+
124+
// for windows, we need to determine its installer type (nsis or squirrel)
125+
if (process.platform === 'win32') {
126+
const isSquirrel = isSquirrelBuild();
127+
if (isSquirrel && file.url.endsWith('.nsis.exe')) {
128+
return;
129+
}
130+
}
131+
132+
files.push(file);
133+
});
134+
135+
if (result.releaseName == null) {
136+
result.releaseName = latestRelease.name;
137+
}
138+
139+
if (result.releaseNotes == null) {
140+
// TODO(@forehalo): add release notes
141+
result.releaseNotes = '';
142+
}
143+
144+
return {
145+
tag: tag,
146+
...result,
147+
};
148+
}
149+
150+
resolveFiles(updateInfo: GithubUpdateInfo): Array<ResolvedUpdateFileInfo> {
151+
const files = getFileList(updateInfo);
152+
153+
return files.map(file => ({
154+
url: new URL(file.url),
155+
info: file,
156+
}));
157+
}
158+
}
159+
160+
function getChannelFilename(channel: string): string {
161+
return `${channel}.yml`;
162+
}

0 commit comments

Comments
 (0)