fix(electron): make sure updater receive correct installer files (#8798)

fix AF-1680
This commit is contained in:
forehalo
2024-11-13 07:48:37 +00:00
parent 01d1631fe8
commit b3b1ea2f33
13 changed files with 356 additions and 122 deletions

View File

@@ -5,7 +5,6 @@ import { newError } from 'builder-util-runtime';
import type {
AppUpdater,
ResolvedUpdateFileInfo,
UpdateFileInfo,
UpdateInfo,
} from 'electron-updater';
import { CancellationToken, Provider } from 'electron-updater';
@@ -23,12 +22,18 @@ interface GithubUpdateInfo extends UpdateInfo {
}
interface GithubRelease {
url: string;
name: string;
tag_name: string;
body: string;
draft: boolean;
prerelease: boolean;
created_at: string;
published_at: string;
assets: Array<{
name: string;
url: string;
size: number;
}>;
}
@@ -92,11 +97,15 @@ export class AFFiNEUpdateProvider extends Provider<GithubUpdateInfo> {
const latestRelease = releases[0] as GithubRelease;
const tag = latestRelease.tag_name;
const channelFileName = getChannelFilename(this.getDefaultChannelName());
const channelFileAsset = latestRelease.assets.find(({ url }) =>
url.endsWith(channelFileName)
const channelFileName = 'latest.yml';
const channelFileAsset = latestRelease.assets.find(
({ name }) => name === channelFileName
);
// TODO(@forehalo):
// we need a way to let UI thread prompt user to manually install the latest version,
// if we introduce breaking changes on auto updater in the future.
// for example we rename the release file from `latest.yml` to `release.yml`
if (!channelFileAsset) {
throw newError(
`Cannot find ${channelFileName} in the latest release artifacts.`,
@@ -113,32 +122,30 @@ export class AFFiNEUpdateProvider extends Provider<GithubUpdateInfo> {
channelFileUrl
);
const files: UpdateFileInfo[] = [];
result.files.forEach(file => {
const asset = latestRelease.assets.find(({ name }) => name === file.url);
if (asset) {
file.url = asset.url;
}
// for windows, we need to determine its installer type (nsis or squirrel)
if (process.platform === 'win32') {
const isSquirrel = isSquirrelBuild();
if (isSquirrel && file.url.endsWith('.nsis.exe')) {
return;
result.files
.filter(({ url }) =>
availableForMyPlatformAndInstaller(
url,
process.platform,
process.arch,
isSquirrelBuild()
)
)
.forEach(file => {
const asset = latestRelease.assets.find(
({ name }) => name === file.url
);
if (asset) {
file.url = asset.url;
}
}
files.push(file);
});
});
if (result.releaseName == null) {
result.releaseName = latestRelease.name;
}
if (result.releaseNotes == null) {
// TODO(@forehalo): add release notes
result.releaseNotes = '';
result.releaseNotes = latestRelease.body;
}
return {
@@ -150,13 +157,130 @@ export class AFFiNEUpdateProvider extends Provider<GithubUpdateInfo> {
resolveFiles(updateInfo: GithubUpdateInfo): Array<ResolvedUpdateFileInfo> {
const files = getFileList(updateInfo);
return files.map(file => ({
url: new URL(file.url),
info: file,
}));
return files
.filter(({ url }) =>
availableForMyPlatformAndInstaller(
url,
process.platform,
process.arch,
isSquirrelBuild()
)
)
.map(file => ({
url: new URL(file.url),
info: file,
}));
}
}
function getChannelFilename(channel: string): string {
return `${channel}.yml`;
type VersionDistribution = 'canary' | 'beta' | 'stable';
type VersionPlatform = 'windows' | 'macos' | 'linux';
type VersionArch = 'x64' | 'arm64';
type FileParts =
| ['affine', string, VersionDistribution, VersionPlatform, VersionArch]
| [
'affine',
string,
`${'canary' | 'beta'}.${number}`,
VersionDistribution,
VersionPlatform,
VersionArch,
];
export function availableForMyPlatformAndInstaller(
file: string,
platform: NodeJS.Platform,
arch: NodeJS.Architecture,
// moved to parameter to make test coverage easier
imWindowsSquirrelPkg: boolean
): boolean {
const imArm64 = arch === 'arm64';
const imX64 = arch === 'x64';
const imMacos = platform === 'darwin';
const imWindows = platform === 'win32';
const imLinux = platform === 'linux';
// in form of:
// affine-${build}-${buildSuffix}-${distribution}-${platform}-${arch}.${installer}
// ^ 1.0.0 ^canary.1 ^ canary ^windows ^ x64 ^.nsis.exe
const filename = file.split('/').pop();
if (!filename) {
return false;
}
const parts = filename.split('-') as FileParts;
// fix -${arch}.${installer}
const archDotInstaller = parts[parts.length - 1];
const installerIdx = archDotInstaller.indexOf('.');
if (installerIdx === -1) {
return false;
}
const installer = archDotInstaller.substring(installerIdx + 1);
parts[parts.length - 1] = archDotInstaller.substring(0, installerIdx);
let version: {
build: string;
suffix?: string;
distribution: VersionDistribution;
platform: VersionPlatform;
arch: VersionArch;
installer: string;
};
if (parts.length === 5) {
version = {
build: parts[1],
distribution: parts[2],
platform: parts[3],
arch: parts[4],
installer,
};
} else if (parts.length === 6) {
version = {
build: parts[1],
suffix: parts[2],
distribution: parts[3],
platform: parts[4],
arch: parts[5],
installer,
};
} else {
return false;
}
function matchPlatform(platform: VersionPlatform) {
return (
(platform === 'windows' && imWindows) ||
(platform === 'macos' && imMacos) ||
(platform === 'linux' && imLinux)
);
}
function matchArch(arch: VersionArch) {
return (
// off course we can install x64 on x64
(imX64 && arch === 'x64') ||
// arm64 macos can install arm64 or x64 in rosetta2
(imArm64 && (arch === 'arm64' || imMacos))
);
}
function matchInstaller(installer: string) {
// do not allow squirrel or nsis installer to cross download each other on windows
if (!imWindows) {
return true;
}
return imWindowsSquirrelPkg
? installer === 'exe'
: installer === 'nsis.exe';
}
return (
matchPlatform(version.platform) &&
matchArch(version.arch) &&
matchInstaller(version.installer)
);
}