mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
fix(electron): make sure updater receive correct installer files (#8798)
fix AF-1680
This commit is contained in:
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user