Skip to content

Commit

Permalink
fix(electron): make sure updater receive correct installer files (#8798)
Browse files Browse the repository at this point in the history
  • Loading branch information
forehalo authored and pull[bot] committed Dec 26, 2024
1 parent 1c5c520 commit 2ea2877
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 122 deletions.
25 changes: 17 additions & 8 deletions packages/frontend/apps/electron/scripts/generate-yml.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ import fs from 'node:fs';
import path from 'node:path';

const filenamesMapping = {
windows: 'latest.yml',
all: 'latest.yml',
macos: 'latest-mac.yml',
linux: 'latest-linux.yml',
};

const releaseFiles = ['zip', 'exe', 'dmg', 'appimage', 'deb', 'flatpak'];

const generateYml = platform => {
const yml = {
version: process.env.RELEASE_VERSION ?? '0.0.0',
files: [],
};
const regex = new RegExp(`^affine-.*-${platform}-.*.(exe|zip|dmg|appimage)$`);

const regex =
// we involves all distribution files in one release file to enforce we handle auto updater correctly
platform === 'all'
? new RegExp(`.(${releaseFiles.join('|')})$`)
: new RegExp(`.+-${platform}-.+.(${releaseFiles.join('|')})$`);

const files = fs.readdirSync(process.cwd()).filter(file => regex.test(file));
const outputFileName = filenamesMapping[platform];

Expand All @@ -34,11 +42,14 @@ const generateYml = platform => {
});
} catch {}
});
// path & sha512 are deprecated
yml.path = yml.files[0].url;
yml.sha512 = yml.files[0].sha512;
yml.releaseDate = new Date().toISOString();

// NOTE(@forehalo): make sure old windows x64 won't fetch windows arm64 by default
// maybe we need to separate arm64 builds to separated yml file `latest-arm64.yml`, `latest-linux-arm64.yml`
// check https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/providers/Provider.ts#L30
// and packages/frontend/apps/electron/src/main/updater/affine-update-provider.ts#L100
yml.files.sort(a => (a.url.includes('windows-arm64') ? 1 : -1));

const ymlStr =
`version: ${yml.version}\n` +
`files:\n` +
Expand All @@ -51,13 +62,11 @@ const generateYml = platform => {
);
})
.join('') +
`path: ${yml.path}\n` +
`sha512: ${yml.sha512}\n` +
`releaseDate: ${yml.releaseDate}\n`;

fs.writeFileSync(outputFileName, ymlStr);
};

generateYml('windows');
generateYml('macos');
generateYml('linux');
generateYml('all');
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
}>;
}

Expand Down Expand Up @@ -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.`,
Expand All @@ -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 {
Expand All @@ -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)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`testing for client update > filter valid installer files > filter for platform [darwin] arch [arm64] 1`] = `
[
"affine-0.18.0-stable-macos-arm64.dmg",
"affine-0.18.0-stable-macos-arm64.zip",
"affine-0.18.0-stable-macos-x64.dmg",
"affine-0.18.0-stable-macos-x64.zip",
]
`;

exports[`testing for client update > filter valid installer files > filter for platform [darwin] arch [x64] 1`] = `
[
"affine-0.18.0-stable-macos-x64.dmg",
"affine-0.18.0-stable-macos-x64.zip",
]
`;

exports[`testing for client update > filter valid installer files > filter for platform [linux] arch [x64] 1`] = `
[
"affine-0.18.0-stable-linux-x64.appimage",
"affine-0.18.0-stable-linux-x64.deb",
"affine-0.18.0-stable-linux-x64.flatpak",
"affine-0.18.0-stable-linux-x64.zip",
]
`;

exports[`testing for client update > filter valid installer files > filter for platform [win32] arch [arm64] 1`] = `
[
"affine-0.18.0-stable-windows-arm64.nsis.exe",
]
`;

exports[`testing for client update > filter valid installer files > filter for platform [win32] arch [arm64] and is squirrel installer 1`] = `
[
"affine-0.18.0-stable-windows-arm64.exe",
]
`;

exports[`testing for client update > filter valid installer files > filter for platform [win32] arch [x64] 1`] = `
[
"affine-0.18.0-stable-windows-x64.nsis.exe",
]
`;

exports[`testing for client update > filter valid installer files > filter for platform [win32] arch [x64] and is squirrel installer 1`] = `
[
"affine-0.18.0-stable-windows-x64.exe",
]
`;
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@
"url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-windows-x64.nsis.exe",
"size": 133493672
},
{
"name": "affine-0.17.0-canary.7-canary-windows-arm64.exe",
"url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-windows-arm64.exe",
"size": 182557416
},
{
"name": "affine-0.17.0-canary.7-canary-windows-arm64.nsis.exe",
"url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-windows-arm64.nsis.exe",
"size": 133493672
},
{
"name": "codecov.yml",
"url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/codecov.yml",
Expand Down
Loading

0 comments on commit 2ea2877

Please sign in to comment.