From 7951fee02fc4e5c7fcfd2b68eb1d77edf6a1f92f Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Mon, 3 Apr 2023 15:46:38 +0800 Subject: [PATCH 01/18] export doSign function --- packages/app-builder-lib/src/codeSign/windowsCodeSign.ts | 4 ++-- packages/app-builder-lib/src/index.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts b/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts index a115836d3b3..06617d87aae 100644 --- a/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts +++ b/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts @@ -52,7 +52,7 @@ export async function sign(options: WindowsSignOptions, packager: WinPackager): hashes = Array.isArray(hashes) ? hashes : [hashes] } - const executor = resolveFunction(options.options.sign, "sign") || doSign + const executor = resolveFunction(options.options.sign, "sign") || DoSign let isNest = false for (const hash of hashes) { const taskConfiguration: WindowsSignTaskConfiguration = { ...options, hash, isNest } @@ -144,7 +144,7 @@ export async function getCertificateFromStoreInfo(options: WindowsConfiguration, throw new Error(`Cannot find certificate ${certificateSubjectName || certificateSha1}, all certs: ${rawResult}`) } -export async function doSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager) { +export async function DoSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager) { // https://github.com/electron-userland/electron-builder/pull/1944 const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT as any, 10) || 10 * 60 * 1000 // decide runtime argument by cases diff --git a/packages/app-builder-lib/src/index.ts b/packages/app-builder-lib/src/index.ts index 7b64409d5c2..d66409c3131 100644 --- a/packages/app-builder-lib/src/index.ts +++ b/packages/app-builder-lib/src/index.ts @@ -46,6 +46,7 @@ export { CustomWindowsSign, FileCodeSigningInfo, CertificateFromStoreInfo, + DoSign, } from "./codeSign/windowsCodeSign" export { CancellationToken, ProgressInfo } from "builder-util-runtime" export { PublishOptions, UploadTask } from "electron-publish" From 1d7fbf1527df445bed5187371eb7cfb7c1e240dc Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Thu, 3 Aug 2023 12:26:25 +0800 Subject: [PATCH 02/18] support DifferentialDownloader in macOS --- packages/electron-updater/src/AppUpdater.ts | 62 +++++++++++++++++++ packages/electron-updater/src/MacUpdater.ts | 8 ++- packages/electron-updater/src/NsisUpdater.ts | 64 +------------------- 3 files changed, 70 insertions(+), 64 deletions(-) diff --git a/packages/electron-updater/src/AppUpdater.ts b/packages/electron-updater/src/AppUpdater.ts index e2a28585060..95d6c9a6abf 100644 --- a/packages/electron-updater/src/AppUpdater.ts +++ b/packages/electron-updater/src/AppUpdater.ts @@ -9,6 +9,8 @@ import { DownloadOptions, CancellationError, ProgressInfo, + BlockMap, + CURRENT_APP_INSTALLER_FILE_NAME, } from "builder-util-runtime" import { randomBytes } from "crypto" import { EventEmitter } from "events" @@ -29,6 +31,10 @@ import { ProviderPlatform } from "./providers/Provider" import type { TypedEmitter } from "tiny-typed-emitter" import Session = Electron.Session import { AuthInfo } from "electron" +import { gunzipSync } from "zlib" +import { blockmapFiles } from "./util" +import { DifferentialDownloaderOptions } from "./differentialDownloader/DifferentialDownloader" +import { GenericDifferentialDownloader } from "./differentialDownloader/GenericDifferentialDownloader" export type AppUpdaterEvents = { error: (error: Error, message?: string) => void @@ -689,6 +695,62 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter log.info(`New version ${version} has been downloaded to ${updateFile}`) return await done(true) } + protected async differentialDownloadInstaller( + fileInfo: ResolvedUpdateFileInfo, + downloadUpdateOptions: DownloadUpdateOptions, + installerPath: string, + provider: Provider + ): Promise { + try { + if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) { + return true + } + const blockmapFileUrls = blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version) + this._logger.info(`Download block maps (old: "${blockmapFileUrls[0]}", new: ${blockmapFileUrls[1]})`) + + const downloadBlockMap = async (url: URL): Promise => { + const data = await this.httpExecutor.downloadToBuffer(url, { + headers: downloadUpdateOptions.requestHeaders, + cancellationToken: downloadUpdateOptions.cancellationToken, + }) + + if (data == null || data.length === 0) { + throw new Error(`Blockmap "${url.href}" is empty`) + } + + try { + return JSON.parse(gunzipSync(data).toString()) + } catch (e: any) { + throw new Error(`Cannot parse blockmap "${url.href}", error: ${e}`) + } + } + + const downloadOptions: DifferentialDownloaderOptions = { + newUrl: fileInfo.url, + oldFile: path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_APP_INSTALLER_FILE_NAME), + logger: this._logger, + newFile: installerPath, + isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest, + requestHeaders: downloadUpdateOptions.requestHeaders, + cancellationToken: downloadUpdateOptions.cancellationToken, + } + + if (this.listenerCount(DOWNLOAD_PROGRESS) > 0) { + downloadOptions.onProgress = it => this.emit(DOWNLOAD_PROGRESS, it) + } + + const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u))) + await new GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1]) + return false + } catch (e: any) { + this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`) + if (this._testOnlyOptions != null) { + // test mode + throw e + } + return true + } + } } export interface DownloadUpdateOptions { diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index 44043bb9a38..03500136c77 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -79,12 +79,16 @@ export class MacUpdater extends AppUpdater { throw newError(`ZIP file not provided: ${safeStringifyJson(files)}`, "ERR_UPDATER_ZIP_FILE_NOT_FOUND") } + const provider = downloadUpdateOptions.updateInfoAndProvider.provider + return this.executeDownload({ fileExtension: "zip", fileInfo: zipFileInfo, downloadUpdateOptions, - task: (destinationFile, downloadOptions) => { - return this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions) + task: async (destinationFile, downloadOptions) => { + if (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider)) { + await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions); + } }, done: event => this.updateDownloaded(zipFileInfo, event), }) diff --git a/packages/electron-updater/src/NsisUpdater.ts b/packages/electron-updater/src/NsisUpdater.ts index c509ed3f0a1..f0f55c237a9 100644 --- a/packages/electron-updater/src/NsisUpdater.ts +++ b/packages/electron-updater/src/NsisUpdater.ts @@ -1,18 +1,15 @@ -import { AllPublishOptions, newError, PackageFileInfo, BlockMap, CURRENT_APP_PACKAGE_FILE_NAME, CURRENT_APP_INSTALLER_FILE_NAME } from "builder-util-runtime" +import { AllPublishOptions, newError, PackageFileInfo, CURRENT_APP_PACKAGE_FILE_NAME } from "builder-util-runtime" import * as path from "path" import { AppAdapter } from "./AppAdapter" import { DownloadUpdateOptions } from "./AppUpdater" import { BaseUpdater, InstallOptions } from "./BaseUpdater" import { DifferentialDownloaderOptions } from "./differentialDownloader/DifferentialDownloader" import { FileWithEmbeddedBlockMapDifferentialDownloader } from "./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader" -import { GenericDifferentialDownloader } from "./differentialDownloader/GenericDifferentialDownloader" -import { DOWNLOAD_PROGRESS, ResolvedUpdateFileInfo, verifyUpdateCodeSignature } from "./main" -import { blockmapFiles } from "./util" +import { DOWNLOAD_PROGRESS, verifyUpdateCodeSignature } from "./main" import { findFile, Provider } from "./providers/Provider" import { unlink } from "fs-extra" import { verifySignature } from "./windowsExecutableCodeSignatureVerifier" import { URL } from "url" -import { gunzipSync } from "zlib" export class NsisUpdater extends BaseUpdater { /** @@ -166,63 +163,6 @@ export class NsisUpdater extends BaseUpdater { return true } - private async differentialDownloadInstaller( - fileInfo: ResolvedUpdateFileInfo, - downloadUpdateOptions: DownloadUpdateOptions, - installerPath: string, - provider: Provider - ): Promise { - try { - if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) { - return true - } - const blockmapFileUrls = blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version) - this._logger.info(`Download block maps (old: "${blockmapFileUrls[0]}", new: ${blockmapFileUrls[1]})`) - - const downloadBlockMap = async (url: URL): Promise => { - const data = await this.httpExecutor.downloadToBuffer(url, { - headers: downloadUpdateOptions.requestHeaders, - cancellationToken: downloadUpdateOptions.cancellationToken, - }) - - if (data == null || data.length === 0) { - throw new Error(`Blockmap "${url.href}" is empty`) - } - - try { - return JSON.parse(gunzipSync(data).toString()) - } catch (e: any) { - throw new Error(`Cannot parse blockmap "${url.href}", error: ${e}`) - } - } - - const downloadOptions: DifferentialDownloaderOptions = { - newUrl: fileInfo.url, - oldFile: path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_APP_INSTALLER_FILE_NAME), - logger: this._logger, - newFile: installerPath, - isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest, - requestHeaders: downloadUpdateOptions.requestHeaders, - cancellationToken: downloadUpdateOptions.cancellationToken, - } - - if (this.listenerCount(DOWNLOAD_PROGRESS) > 0) { - downloadOptions.onProgress = it => this.emit(DOWNLOAD_PROGRESS, it) - } - - const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u))) - await new GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1]) - return false - } catch (e: any) { - this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`) - if (this._testOnlyOptions != null) { - // test mode - throw e - } - return true - } - } - private async differentialDownloadWebPackage( downloadUpdateOptions: DownloadUpdateOptions, packageInfo: PackageFileInfo, From de77149d04262cc0f0e8235780bcdbcdf6b241e5 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Thu, 3 Aug 2023 12:47:20 +0800 Subject: [PATCH 03/18] update Dosign to doSign --- packages/app-builder-lib/src/codeSign/windowsCodeSign.ts | 4 ++-- packages/app-builder-lib/src/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts b/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts index 06617d87aae..a115836d3b3 100644 --- a/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts +++ b/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts @@ -52,7 +52,7 @@ export async function sign(options: WindowsSignOptions, packager: WinPackager): hashes = Array.isArray(hashes) ? hashes : [hashes] } - const executor = resolveFunction(options.options.sign, "sign") || DoSign + const executor = resolveFunction(options.options.sign, "sign") || doSign let isNest = false for (const hash of hashes) { const taskConfiguration: WindowsSignTaskConfiguration = { ...options, hash, isNest } @@ -144,7 +144,7 @@ export async function getCertificateFromStoreInfo(options: WindowsConfiguration, throw new Error(`Cannot find certificate ${certificateSubjectName || certificateSha1}, all certs: ${rawResult}`) } -export async function DoSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager) { +export async function doSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager) { // https://github.com/electron-userland/electron-builder/pull/1944 const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT as any, 10) || 10 * 60 * 1000 // decide runtime argument by cases diff --git a/packages/app-builder-lib/src/index.ts b/packages/app-builder-lib/src/index.ts index d66409c3131..357e4992952 100644 --- a/packages/app-builder-lib/src/index.ts +++ b/packages/app-builder-lib/src/index.ts @@ -46,7 +46,7 @@ export { CustomWindowsSign, FileCodeSigningInfo, CertificateFromStoreInfo, - DoSign, + doSign, } from "./codeSign/windowsCodeSign" export { CancellationToken, ProgressInfo } from "builder-util-runtime" export { PublishOptions, UploadTask } from "electron-publish" From 6051063cbc4b5b799687ec41e9cd3162b81e98c7 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Thu, 3 Aug 2023 12:48:32 +0800 Subject: [PATCH 04/18] delete dosign --- packages/app-builder-lib/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/app-builder-lib/src/index.ts b/packages/app-builder-lib/src/index.ts index 357e4992952..7b64409d5c2 100644 --- a/packages/app-builder-lib/src/index.ts +++ b/packages/app-builder-lib/src/index.ts @@ -46,7 +46,6 @@ export { CustomWindowsSign, FileCodeSigningInfo, CertificateFromStoreInfo, - doSign, } from "./codeSign/windowsCodeSign" export { CancellationToken, ProgressInfo } from "builder-util-runtime" export { PublishOptions, UploadTask } from "electron-publish" From 3f481960fbb49ff0063bb59e95f648245279f472 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Fri, 4 Aug 2023 01:12:14 +0800 Subject: [PATCH 05/18] save zip file --- packages/electron-updater/src/AppUpdater.ts | 6 +++--- packages/electron-updater/src/MacUpdater.ts | 9 +++++---- packages/electron-updater/src/NsisUpdater.ts | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/electron-updater/src/AppUpdater.ts b/packages/electron-updater/src/AppUpdater.ts index 95d6c9a6abf..3a0557322b7 100644 --- a/packages/electron-updater/src/AppUpdater.ts +++ b/packages/electron-updater/src/AppUpdater.ts @@ -10,7 +10,6 @@ import { CancellationError, ProgressInfo, BlockMap, - CURRENT_APP_INSTALLER_FILE_NAME, } from "builder-util-runtime" import { randomBytes } from "crypto" import { EventEmitter } from "events" @@ -699,7 +698,8 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter fileInfo: ResolvedUpdateFileInfo, downloadUpdateOptions: DownloadUpdateOptions, installerPath: string, - provider: Provider + provider: Provider, + oldInstallerFileName: string ): Promise { try { if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) { @@ -727,7 +727,7 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter const downloadOptions: DifferentialDownloaderOptions = { newUrl: fileInfo.url, - oldFile: path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_APP_INSTALLER_FILE_NAME), + oldFile: path.join(this.downloadedUpdateHelper!.cacheDir, oldInstallerFileName), logger: this._logger, newFile: installerPath, isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest, diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index 03500136c77..b8a8369448f 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -1,6 +1,6 @@ -import { AllPublishOptions, newError, safeStringifyJson } from "builder-util-runtime" +import { AllPublishOptions, newError, safeStringifyJson, CURRENT_MAC_APP_ZIP_FILE_NAME } from "builder-util-runtime" import { stat } from "fs-extra" -import { createReadStream } from "fs" +import { createReadStream, copyFileSync } from "fs" import { createServer, IncomingMessage, Server, ServerResponse } from "http" import { AppAdapter } from "./AppAdapter" import { AppUpdater, DownloadUpdateOptions } from "./AppUpdater" @@ -86,9 +86,10 @@ export class MacUpdater extends AppUpdater { fileInfo: zipFileInfo, downloadUpdateOptions, task: async (destinationFile, downloadOptions) => { - if (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider)) { - await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions); + if (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME)) { + await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions) } + copyFileSync(destinationFile, this.downloadedUpdateHelper!.cacheDir + "/update.zip") }, done: event => this.updateDownloaded(zipFileInfo, event), }) diff --git a/packages/electron-updater/src/NsisUpdater.ts b/packages/electron-updater/src/NsisUpdater.ts index f0f55c237a9..d8a353e68ed 100644 --- a/packages/electron-updater/src/NsisUpdater.ts +++ b/packages/electron-updater/src/NsisUpdater.ts @@ -1,4 +1,4 @@ -import { AllPublishOptions, newError, PackageFileInfo, CURRENT_APP_PACKAGE_FILE_NAME } from "builder-util-runtime" +import { AllPublishOptions, newError, PackageFileInfo, CURRENT_APP_INSTALLER_FILE_NAME, CURRENT_APP_PACKAGE_FILE_NAME } from "builder-util-runtime" import * as path from "path" import { AppAdapter } from "./AppAdapter" import { DownloadUpdateOptions } from "./AppUpdater" @@ -61,7 +61,7 @@ export class NsisUpdater extends BaseUpdater { "disableWebInstaller is set to false, you should set it to true if you do not plan on using a web installer. This will default to true in a future version." ) } - if (isWebInstaller || (await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, provider))) { + if (isWebInstaller || (await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_APP_INSTALLER_FILE_NAME))) { await this.httpExecutor.download(fileInfo.url, destinationFile, downloadOptions) } From bd976803b5976ad0b85ac0864195a62a7219591d Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Sun, 13 Aug 2023 21:04:00 +0800 Subject: [PATCH 06/18] export CURRENT_MAC_APP_ZIP_FILE_NAME --- packages/builder-util-runtime/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/builder-util-runtime/src/index.ts b/packages/builder-util-runtime/src/index.ts index b3d845ac8de..f9b3642f64e 100644 --- a/packages/builder-util-runtime/src/index.ts +++ b/packages/builder-util-runtime/src/index.ts @@ -40,6 +40,8 @@ export { BlockMap } from "./blockMapApi" export const CURRENT_APP_INSTALLER_FILE_NAME = "installer.exe" // nsis-web export const CURRENT_APP_PACKAGE_FILE_NAME = "package.7z" +// mac zip +export const CURRENT_MAC_APP_ZIP_FILE_NAME = "update.zip" export function asArray(v: null | undefined | T | Array): Array { if (v == null) { From 8491d3e5c8a3ce3356ddc5b7911212f865d5d1bb Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Sun, 27 Aug 2023 17:34:17 +0800 Subject: [PATCH 07/18] add ut --- test/NUL | 0 test/src/updater/differentialUpdateTest.ts | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 test/NUL diff --git a/test/NUL b/test/NUL new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index ff06dab5a71..75a0f67017c 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -171,22 +171,25 @@ test.ifAll.ifDevOrLinuxCi("AppImage", () => testLinux(Arch.x64)) test.ifAll.ifDevOrLinuxCi("AppImage ia32", () => testLinux(Arch.ia32)) -// ifAll.ifMac.ifNotCi todo -test.skip("dmg", async () => { +async function testMac(arch: Arch) { + process.env.TEST_UPDATER_ARCH = Arch[arch] + const outDirs: Array = [] const tmpDir = new TmpDir("differential-updater-test") - if (process.env.__SKIP_BUILD == null) { - await doBuild(outDirs, Platform.MAC.createTarget(undefined, Arch.x64), tmpDir, { + try { + await doBuild(outDirs, Platform.MAC.createTarget(["dmg"], arch), tmpDir, { mac: { electronUpdaterCompatibility: ">=2.17.0", }, }) - } else { - // todo + await testBlockMap(outDirs[0], path.join(outDirs[1]), MacUpdater, "mac/Test App ßW.app", Platform.MAC) + } finally { + await tmpDir.cleanup() } +} - await testBlockMap(outDirs[0], path.join(outDirs[1]), MacUpdater, "mac/Test App ßW.app", Platform.MAC) -}) +test.ifAll.ifMac.ifNotCi("Mac intel", () => testMac(Arch.x64)) +test.ifAll.ifMac.ifNotCi("Mac arm64", () => testMac(Arch.arm64)) async function buildApp(version: string, outDirs: Array, targets: Map>>, tmpDir: TmpDir, extraConfig: Configuration | null | undefined) { await assertPack( From 0470a07becdf691b399dae8d36c0e96d37ca6473 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Sun, 27 Aug 2023 17:36:16 +0800 Subject: [PATCH 08/18] delete NUL file --- test/NUL | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/NUL diff --git a/test/NUL b/test/NUL deleted file mode 100644 index e69de29bb2d..00000000000 From f305499565a2e99816e0298fa71e8893464aa265 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Wed, 21 Feb 2024 17:21:04 +0800 Subject: [PATCH 09/18] fix ut --- test/src/updater/differentialUpdateTest.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index 75a0f67017c..72486b04bc6 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -176,16 +176,14 @@ async function testMac(arch: Arch) { const outDirs: Array = [] const tmpDir = new TmpDir("differential-updater-test") - try { - await doBuild(outDirs, Platform.MAC.createTarget(["dmg"], arch), tmpDir, { - mac: { - electronUpdaterCompatibility: ">=2.17.0", - }, - }) - await testBlockMap(outDirs[0], path.join(outDirs[1]), MacUpdater, "mac/Test App ßW.app", Platform.MAC) - } finally { - await tmpDir.cleanup() - } + await doBuild(outDirs, Platform.MAC.createTarget(["pkg", "zip"], arch), tmpDir, { + mac: { + electronUpdaterCompatibility: ">=2.17.0", + }, + }) + await testBlockMap(outDirs[0], path.join(outDirs[1]), MacUpdater, "mac/Test App ßW.app", Platform.MAC) + + await tmpDir.cleanup() } test.ifAll.ifMac.ifNotCi("Mac intel", () => testMac(Arch.x64)) From afa25056bf9dbd26c4adc3133e926508f0cad0ab Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Fri, 23 Feb 2024 17:39:12 -0800 Subject: [PATCH 10/18] add universal test --- test/snapshots/updater/differentialUpdateTest.js.snap | 10 ---------- test/src/updater/differentialUpdateTest.ts | 8 +++++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/test/snapshots/updater/differentialUpdateTest.js.snap b/test/snapshots/updater/differentialUpdateTest.js.snap index 906eb738189..05c6bc5b00e 100644 --- a/test/snapshots/updater/differentialUpdateTest.js.snap +++ b/test/snapshots/updater/differentialUpdateTest.js.snap @@ -199,11 +199,6 @@ Object { "version": "1.0.0", }, }, - Object { - "arch": "x64", - "file": "Test App ßW-1.0.0.pkg", - "safeArtifactName": "TestApp-1.0.0.pkg", - }, Object { "arch": "x64", "file": "Test App ßW-1.0.0-mac.zip", @@ -282,11 +277,6 @@ Object { "version": "1.0.1", }, }, - Object { - "arch": "x64", - "file": "Test App ßW-1.0.1.pkg", - "safeArtifactName": "TestApp-1.0.1.pkg", - }, Object { "arch": "x64", "file": "Test App ßW-1.0.1-mac.zip", diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index 9cd65dde7d3..1d75488770c 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -2,7 +2,7 @@ import { Arch, Configuration, Platform } from "app-builder-lib" import { getBinFromUrl } from "app-builder-lib/out/binDownload" import { doSpawn } from "builder-util" import { GenericServerOptions, S3Options } from "builder-util-runtime" -import { AppImageUpdater, MacUpdater, NsisUpdater } from "electron-updater" +import { AppImageUpdater, BaseUpdater, MacUpdater, NsisUpdater } from "electron-updater" import { EventEmitter } from "events" import { move } from "fs-extra" import * as path from "path" @@ -120,7 +120,7 @@ async function testMac(arch: Arch) { const outDirs: Array = [] const tmpDir = new TmpDir("differential-updater-test") try { - await doBuild(outDirs, Platform.MAC.createTarget(["pkg", "zip"], arch), tmpDir, false, { + await doBuild(outDirs, Platform.MAC.createTarget(["zip"], arch), tmpDir, false, { mac: { electronUpdaterCompatibility: ">=2.17.0", }, @@ -135,7 +135,9 @@ test.ifMac("Mac Intel", () => testMac(Arch.x64)) test.ifMac("Mac arm64", () => testMac(Arch.arm64)) -async function checkResult(updater: NsisUpdater) { +test.ifMac("Mac universal", () => testMac(Arch.universal)) + +async function checkResult(updater: BaseUpdater) { const updateCheckResult = await updater.checkForUpdates() const downloadPromise = updateCheckResult?.downloadPromise // noinspection JSIgnoredPromiseFromCall From 81f890db0914a7807e9841d3370e81c9295b8f22 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Sat, 24 Feb 2024 09:02:25 -0800 Subject: [PATCH 11/18] get initial server running with correct files --- test/src/updater/differentialUpdateTest.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index 1d75488770c..cc184de68a2 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -1,6 +1,6 @@ import { Arch, Configuration, Platform } from "app-builder-lib" import { getBinFromUrl } from "app-builder-lib/out/binDownload" -import { doSpawn } from "builder-util" +import { doSpawn, getArchSuffix } from "builder-util" import { GenericServerOptions, S3Options } from "builder-util-runtime" import { AppImageUpdater, BaseUpdater, MacUpdater, NsisUpdater } from "electron-updater" import { EventEmitter } from "events" @@ -80,7 +80,7 @@ test.ifWindows("web installer", async () => { const oldDir = outDirs[0] await move(path.join(oldDir, "nsis-web", `TestApp-${OLD_VERSION_NUMBER}-x64.nsis.7z`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "package.7z")) - await testBlockMap(outDirs[0], path.join(outDirs[1], "nsis-web"), NsisUpdater, "win-unpacked", Platform.WINDOWS) + await testBlockMap(outDirs[0], path.join(outDirs[1], "nsis-web"), NsisUpdater, Platform.WINDOWS, Arch.x64) }) test.ifWindows("nsis", async () => { @@ -89,10 +89,11 @@ test.ifWindows("nsis", async () => { await doBuild(outDirs, Platform.WINDOWS.createTarget(["nsis"], Arch.x64), tmpDir, true) const oldDir = outDirs[0] + // move to new dir so that localhost server can read both blockmaps await move(path.join(oldDir, `Test App ßW Setup ${OLD_VERSION_NUMBER}.exe`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "installer.exe")) await move(path.join(oldDir, `Test App ßW Setup ${OLD_VERSION_NUMBER}.exe.blockmap`), path.join(outDirs[1], "Test App ßW Setup 1.0.0.exe.blockmap")) - await testBlockMap(outDirs[0], outDirs[1], NsisUpdater, "win-unpacked", Platform.WINDOWS) + await testBlockMap(outDirs[0], outDirs[1], NsisUpdater, Platform.WINDOWS, Arch.x64) }) async function testLinux(arch: Arch) { @@ -104,7 +105,7 @@ async function testLinux(arch: Arch) { await doBuild(outDirs, Platform.LINUX.createTarget(["appimage"], arch), tmpDir, false) process.env.APPIMAGE = path.join(outDirs[0], `Test App ßW-${OLD_VERSION_NUMBER}${arch === Arch.ia32 ? "-i386" : ""}.AppImage`) - await testBlockMap(outDirs[0], outDirs[1], AppImageUpdater, `linux-${arch === Arch.ia32 ? "ia32-" : ""}unpacked`, Platform.LINUX) + await testBlockMap(outDirs[0], outDirs[1], AppImageUpdater, Platform.LINUX, arch) } finally { await tmpDir.cleanup() } @@ -125,7 +126,12 @@ async function testMac(arch: Arch) { electronUpdaterCompatibility: ">=2.17.0", }, }) - await testBlockMap(outDirs[0], outDirs[1], MacUpdater, "mac/Test App ßW.app", Platform.MAC) + const oldDir = outDirs[0] + // move to new dir so that localhost server can read both blockmaps + await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "update.zip")) + await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}-mac.zip.blockmap`), path.join(outDirs[1], "Test App ßW-1.0.0-mac.zip.blockmap")) + + await testBlockMap(outDirs[0], outDirs[1], MacUpdater, Platform.MAC, arch, "Test App ßW") } finally { await tmpDir.cleanup() } @@ -181,7 +187,11 @@ function getTestUpdaterCacheDir(oldDir: string) { return path.join(oldDir, "updater-cache") } -async function testBlockMap(oldDir: string, newDir: string, updaterClass: any, appUpdateConfigPath: string, platform: Platform) { +async function testBlockMap(oldDir: string, newDir: string, updaterClass: any, platform: Platform, arch: Arch, productFilename?: string) { + const appUpdateConfigPath = path.join( + `${platform.buildConfigurationKey}${getArchSuffix(arch)}${platform === Platform.MAC ? "" : "-unpacked"}`, + platform === Platform.MAC ? `${productFilename}.app` : "" + ) const port = 8000 + (updaterClass.name.charCodeAt(0) as number) + Math.floor(Math.random() * 10000) // noinspection SpellCheckingInspection From 696bb5125b7d32705cfa5cc13dbbd98e6de0ff43 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Sat, 24 Feb 2024 09:34:43 -0800 Subject: [PATCH 12/18] cleanup --- packages/electron-updater/src/MacUpdater.ts | 18 +- .../updater/differentialUpdateTest.js.snap | 229 +++++++++++++++++- test/src/updater/differentialUpdateTest.ts | 46 ++-- 3 files changed, 251 insertions(+), 42 deletions(-) diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index b8a8369448f..28d5f6d2a31 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -26,6 +26,7 @@ export class MacUpdater extends AppUpdater { }) this.nativeUpdater.on("update-downloaded", () => { this.squirrelDownloadedUpdate = true + this.debug("nativeUpdater.update-downloaded") }) } @@ -35,6 +36,17 @@ export class MacUpdater extends AppUpdater { } } + private closeServerIfExists() { + if (this.server) { + this.debug("Closing proxy server") + this.server.close(err => { + if (err) { + this.debug("proxy server wasn't already open, probably attempted closing again as a safety check before quit") + } + }) + } + } + protected async doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise> { let files = downloadUpdateOptions.updateInfoAndProvider.provider.resolveFiles(downloadUpdateOptions.updateInfoAndProvider.info) @@ -101,8 +113,8 @@ export class MacUpdater extends AppUpdater { const log = this._logger const logContext = `fileToProxy=${zipFileInfo.url.href}` + this.closeServerIfExists() this.debug(`Creating proxy server for native Squirrel.Mac (${logContext})`) - this.server?.close() this.server = createServer() this.debug(`Proxy server for native Squirrel.Mac is created (${logContext})`) this.server.on("close", () => { @@ -221,12 +233,12 @@ export class MacUpdater extends AppUpdater { if (this.squirrelDownloadedUpdate) { // update already fetched by Squirrel, it's ready to install this.nativeUpdater.quitAndInstall() - this.server?.close() + this.closeServerIfExists() } else { // Quit and install as soon as Squirrel get the update this.nativeUpdater.on("update-downloaded", () => { this.nativeUpdater.quitAndInstall() - this.server?.close() + this.closeServerIfExists() }) if (!this.autoInstallOnAppQuit) { diff --git a/test/snapshots/updater/differentialUpdateTest.js.snap b/test/snapshots/updater/differentialUpdateTest.js.snap index 05c6bc5b00e..bb158b6ca60 100644 --- a/test/snapshots/updater/differentialUpdateTest.js.snap +++ b/test/snapshots/updater/differentialUpdateTest.js.snap @@ -336,6 +336,27 @@ Object { } `; +exports[`Mac Intel 5`] = ` +Object { + "files": Array [ + Object { + "sha512": "@sha512", + "size": "@size", + }, + ], + "path": "Test App ßW-1.0.1-mac.zip", + "releaseDate": "@releaseDate", + "sha512": "@sha512", + "version": "1.0.1", +} +`; + +exports[`Mac Intel 6`] = ` +Array [ + "Test App ßW-1.0.1-mac.zip", +] +`; + exports[`Mac arm64 1`] = ` Object { "mac": Array [ @@ -355,11 +376,6 @@ Object { "version": "1.0.0", }, }, - Object { - "arch": "arm64", - "file": "Test App ßW-1.0.0-arm64.pkg", - "safeArtifactName": "TestApp-1.0.0-arm64.pkg", - }, Object { "arch": "arm64", "file": "Test App ßW-1.0.0-arm64-mac.zip", @@ -438,11 +454,6 @@ Object { "version": "1.0.1", }, }, - Object { - "arch": "arm64", - "file": "Test App ßW-1.0.1-arm64.pkg", - "safeArtifactName": "TestApp-1.0.1-arm64.pkg", - }, Object { "arch": "arm64", "file": "Test App ßW-1.0.1-arm64-mac.zip", @@ -502,6 +513,204 @@ Object { } `; +exports[`Mac arm64 5`] = ` +Object { + "files": Array [ + Object { + "sha512": "@sha512", + "size": "@size", + }, + ], + "path": "Test App ßW-1.0.1-arm64-mac.zip", + "releaseDate": "@releaseDate", + "sha512": "@sha512", + "version": "1.0.1", +} +`; + +exports[`Mac arm64 6`] = ` +Array [ + "Test App ßW-1.0.1-arm64-mac.zip", +] +`; + +exports[`Mac universal 1`] = ` +Object { + "mac": Array [ + Object { + "file": "latest-mac.yml", + "fileContent": Object { + "files": Array [ + Object { + "sha512": "@sha512", + "size": "@size", + "url": "Test App ßW-1.0.0-universal-mac.zip", + }, + ], + "path": "Test App ßW-1.0.0-universal-mac.zip", + "releaseDate": "@releaseDate", + "sha512": "@sha512", + "version": "1.0.0", + }, + }, + Object { + "arch": "universal", + "file": "Test App ßW-1.0.0-universal-mac.zip", + "safeArtifactName": "TestApp-1.0.0-universal-mac.zip", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + Object { + "file": "Test App ßW-1.0.0-universal-mac.zip.blockmap", + "safeArtifactName": "Test App ßW-1.0.0-universal-mac.zip.blockmap", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`Mac universal 2`] = ` +Object { + "CFBundleDisplayName": "Test App ßW", + "CFBundleExecutable": "Test App ßW", + "CFBundleIconFile": "icon.icns", + "CFBundleIdentifier": "org.electron-builder.testApp", + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundleName": "Test App ßW", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "1.0.0", + "LSApplicationCategoryType": "your.app.category.type", + "NSAppTransportSecurity": Object { + "NSAllowsLocalNetworking": true, + "NSExceptionDomains": Object { + "127.0.0.1": Object { + "NSIncludesSubdomains": false, + "NSTemporaryExceptionAllowsInsecureHTTPLoads": true, + "NSTemporaryExceptionAllowsInsecureHTTPSLoads": false, + "NSTemporaryExceptionMinimumTLSVersion": "1.0", + "NSTemporaryExceptionRequiresForwardSecrecy": false, + }, + "localhost": Object { + "NSIncludesSubdomains": false, + "NSTemporaryExceptionAllowsInsecureHTTPLoads": true, + "NSTemporaryExceptionAllowsInsecureHTTPSLoads": false, + "NSTemporaryExceptionMinimumTLSVersion": "1.0", + "NSTemporaryExceptionRequiresForwardSecrecy": false, + }, + }, + }, + "NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth", + "NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth", + "NSHighResolutionCapable": true, + "NSPrincipalClass": "AtomApplication", + "NSSupportsAutomaticGraphicsSwitching": true, +} +`; + +exports[`Mac universal 3`] = ` +Object { + "mac": Array [ + Object { + "file": "latest-mac.yml", + "fileContent": Object { + "files": Array [ + Object { + "sha512": "@sha512", + "size": "@size", + "url": "Test App ßW-1.0.1-universal-mac.zip", + }, + ], + "path": "Test App ßW-1.0.1-universal-mac.zip", + "releaseDate": "@releaseDate", + "sha512": "@sha512", + "version": "1.0.1", + }, + }, + Object { + "arch": "universal", + "file": "Test App ßW-1.0.1-universal-mac.zip", + "safeArtifactName": "TestApp-1.0.1-universal-mac.zip", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + Object { + "file": "Test App ßW-1.0.1-universal-mac.zip.blockmap", + "safeArtifactName": "Test App ßW-1.0.1-universal-mac.zip.blockmap", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`Mac universal 4`] = ` +Object { + "CFBundleDisplayName": "Test App ßW", + "CFBundleExecutable": "Test App ßW", + "CFBundleIconFile": "icon.icns", + "CFBundleIdentifier": "org.electron-builder.testApp", + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundleName": "Test App ßW", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "1.0.1", + "LSApplicationCategoryType": "your.app.category.type", + "NSAppTransportSecurity": Object { + "NSAllowsLocalNetworking": true, + "NSExceptionDomains": Object { + "127.0.0.1": Object { + "NSIncludesSubdomains": false, + "NSTemporaryExceptionAllowsInsecureHTTPLoads": true, + "NSTemporaryExceptionAllowsInsecureHTTPSLoads": false, + "NSTemporaryExceptionMinimumTLSVersion": "1.0", + "NSTemporaryExceptionRequiresForwardSecrecy": false, + }, + "localhost": Object { + "NSIncludesSubdomains": false, + "NSTemporaryExceptionAllowsInsecureHTTPLoads": true, + "NSTemporaryExceptionAllowsInsecureHTTPSLoads": false, + "NSTemporaryExceptionMinimumTLSVersion": "1.0", + "NSTemporaryExceptionRequiresForwardSecrecy": false, + }, + }, + }, + "NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth", + "NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth", + "NSHighResolutionCapable": true, + "NSPrincipalClass": "AtomApplication", + "NSSupportsAutomaticGraphicsSwitching": true, +} +`; + +exports[`Mac universal 5`] = ` +Object { + "files": Array [ + Object { + "sha512": "@sha512", + "size": "@size", + }, + ], + "path": "Test App ßW-1.0.1-universal-mac.zip", + "releaseDate": "@releaseDate", + "sha512": "@sha512", + "version": "1.0.1", +} +`; + +exports[`Mac universal 6`] = ` +Array [ + "Test App ßW-1.0.1-universal-mac.zip", +] +`; + exports[`nsis 1`] = ` Object { "win": Array [ diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index cc184de68a2..516b129566d 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -126,10 +126,12 @@ async function testMac(arch: Arch) { electronUpdaterCompatibility: ">=2.17.0", }, }) - const oldDir = outDirs[0] + // move to new dir so that localhost server can read both blockmaps - await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "update.zip")) - await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}-mac.zip.blockmap`), path.join(outDirs[1], "Test App ßW-1.0.0-mac.zip.blockmap")) + const oldDir = outDirs[0] + const blockmap = `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip.blockmap` + await move(path.join(oldDir, blockmap), path.join(outDirs[1], blockmap)) + await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "update.zip")) await testBlockMap(outDirs[0], outDirs[1], MacUpdater, Platform.MAC, arch, "Test App ßW") } finally { @@ -144,6 +146,9 @@ test.ifMac("Mac arm64", () => testMac(Arch.arm64)) test.ifMac("Mac universal", () => testMac(Arch.universal)) async function checkResult(updater: BaseUpdater) { + // disable automatic install otherwise mac updater will permanently wait on mocked electron's native updater to receive update (mocked server can't install) + updater.autoInstallOnAppQuit = false + const updateCheckResult = await updater.checkForUpdates() const downloadPromise = updateCheckResult?.downloadPromise // noinspection JSIgnoredPromiseFromCall @@ -151,7 +156,7 @@ async function checkResult(updater: BaseUpdater) { const files = await downloadPromise const fileInfo: any = updateCheckResult?.updateInfo.files[0] - // because port is random + // delete url because port is random expect(fileInfo.url).toBeDefined() delete fileInfo.url expect(removeUnstableProperties(updateCheckResult?.updateInfo)).toMatchSnapshot() @@ -159,27 +164,13 @@ async function checkResult(updater: BaseUpdater) { } class TestNativeUpdater extends EventEmitter { - // private updateUrl: string | null = null - - // noinspection JSMethodCanBeStatic checkForUpdates() { console.log("TestNativeUpdater.checkForUpdates") // MacUpdater expects this to emit corresponding update-downloaded event this.emit("update-downloaded") - // this.download() - // .catch(error => { - // this.emit("error", error) - // }) } - - // private async download() { - // } - - // noinspection JSMethodCanBeStatic - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setFeedURL(_updateUrl: string) { - // console.log("TestNativeUpdater.setFeedURL " + updateUrl) - // this.updateUrl = updateUrl + setFeedURL(updateConfig: any) { + console.log("TestNativeUpdater.setFeedURL " + updateConfig.url) } } @@ -194,11 +185,10 @@ async function testBlockMap(oldDir: string, newDir: string, updaterClass: any, p ) const port = 8000 + (updaterClass.name.charCodeAt(0) as number) + Math.floor(Math.random() * 10000) - // noinspection SpellCheckingInspection - const httpServerProcess = doSpawn( - path.join(await getBinFromUrl("ran", "0.1.3", "imfA3LtT6umMM0BuQ29MgO3CJ9uleN5zRBi3sXzcTbMOeYZ6SQeN7eKr3kXZikKnVOIwbH+DDO43wkiR/qTdkg=="), process.platform, "ran"), - [`-root=${newDir}`, `-port=${port}`, "-gzip=false", "-listdir=true"] - ) + const serverBin = await getBinFromUrl("ran", "0.1.3", "imfA3LtT6umMM0BuQ29MgO3CJ9uleN5zRBi3sXzcTbMOeYZ6SQeN7eKr3kXZikKnVOIwbH+DDO43wkiR/qTdkg==") + const httpServerProcess = doSpawn(path.join(serverBin, process.platform, "ran"), [`-root=${newDir}`, `-port=${port}`, "-gzip=false", "-listdir=true"]) + + // Mac uses electron's native autoUpdater to serve updates to, we mock here since electron API isn't available within jest runtime const mockNativeUpdater = new TestNativeUpdater() jest.mock( "electron", @@ -210,7 +200,7 @@ async function testBlockMap(oldDir: string, newDir: string, updaterClass: any, p { virtual: true } ) - return await new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { httpServerProcess.on("error", reject) const updater = new updaterClass(null, new TestAppAdapter(OLD_VERSION_NUMBER, getTestUpdaterCacheDir(oldDir))) @@ -240,9 +230,7 @@ async function testBlockMap(oldDir: string, newDir: string, updaterClass: any, p await checkResult(updater) } - doTest() - .then(() => resolve(null)) - .catch(reject) + doTest().then(resolve).catch(reject) }).then( v => { httpServerProcess.kill() From 91dc5988be86f62bcc4c2eea8fc4589ffbbb3f9f Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Sat, 24 Feb 2024 09:59:59 -0800 Subject: [PATCH 13/18] conditionally run arm64 mac differential test only on arm64 macs --- test/src/updater/differentialUpdateTest.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index 516b129566d..450b1ef24f0 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -140,10 +140,11 @@ async function testMac(arch: Arch) { } test.ifMac("Mac Intel", () => testMac(Arch.x64)) +test.ifMac("Mac universal", () => testMac(Arch.universal)) -test.ifMac("Mac arm64", () => testMac(Arch.arm64)) +// only run on arm64 macs, otherwise of course no files can be found to be updated to (due to arch mismatch) +test.ifMac.ifEnv(process.arch === "arm64")("Mac arm64", () => testMac(Arch.arm64)) -test.ifMac("Mac universal", () => testMac(Arch.universal)) async function checkResult(updater: BaseUpdater) { // disable automatic install otherwise mac updater will permanently wait on mocked electron's native updater to receive update (mocked server can't install) From 5d1ec7e3b9b39c0c9af3df066954adec3dea1fa7 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 25 Feb 2024 16:42:29 +0800 Subject: [PATCH 14/18] move CURRENT_MAC_APP_ZIP_FILE_NAME to MacUpdater.ts --- packages/builder-util-runtime/src/index.ts | 2 -- packages/electron-updater/src/MacUpdater.ts | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/builder-util-runtime/src/index.ts b/packages/builder-util-runtime/src/index.ts index f9b3642f64e..b3d845ac8de 100644 --- a/packages/builder-util-runtime/src/index.ts +++ b/packages/builder-util-runtime/src/index.ts @@ -40,8 +40,6 @@ export { BlockMap } from "./blockMapApi" export const CURRENT_APP_INSTALLER_FILE_NAME = "installer.exe" // nsis-web export const CURRENT_APP_PACKAGE_FILE_NAME = "package.7z" -// mac zip -export const CURRENT_MAC_APP_ZIP_FILE_NAME = "update.zip" export function asArray(v: null | undefined | T | Array): Array { if (v == null) { diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index 28d5f6d2a31..87630be867e 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -1,6 +1,7 @@ -import { AllPublishOptions, newError, safeStringifyJson, CURRENT_MAC_APP_ZIP_FILE_NAME } from "builder-util-runtime" +import { AllPublishOptions, newError, safeStringifyJson } from "builder-util-runtime" import { stat } from "fs-extra" import { createReadStream, copyFileSync } from "fs" +import * as path from "path" import { createServer, IncomingMessage, Server, ServerResponse } from "http" import { AppAdapter } from "./AppAdapter" import { AppUpdater, DownloadUpdateOptions } from "./AppUpdater" @@ -92,6 +93,7 @@ export class MacUpdater extends AppUpdater { } const provider = downloadUpdateOptions.updateInfoAndProvider.provider + const CURRENT_MAC_APP_ZIP_FILE_NAME = "updater.zip" return this.executeDownload({ fileExtension: "zip", @@ -101,7 +103,7 @@ export class MacUpdater extends AppUpdater { if (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME)) { await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions) } - copyFileSync(destinationFile, this.downloadedUpdateHelper!.cacheDir + "/update.zip") + copyFileSync(destinationFile, path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_MAC_APP_ZIP_FILE_NAME)) }, done: event => this.updateDownloaded(zipFileInfo, event), }) From 354b4d98398cea9fba934c3320f0bc56441551c8 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Mon, 26 Feb 2024 16:03:27 +0800 Subject: [PATCH 15/18] fix ut --- test/src/updater/differentialUpdateTest.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index 450b1ef24f0..e7042f3a916 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -131,7 +131,7 @@ async function testMac(arch: Arch) { const oldDir = outDirs[0] const blockmap = `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip.blockmap` await move(path.join(oldDir, blockmap), path.join(outDirs[1], blockmap)) - await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "update.zip")) + await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "updater.zip")) await testBlockMap(outDirs[0], outDirs[1], MacUpdater, Platform.MAC, arch, "Test App ßW") } finally { @@ -145,7 +145,6 @@ test.ifMac("Mac universal", () => testMac(Arch.universal)) // only run on arm64 macs, otherwise of course no files can be found to be updated to (due to arch mismatch) test.ifMac.ifEnv(process.arch === "arm64")("Mac arm64", () => testMac(Arch.arm64)) - async function checkResult(updater: BaseUpdater) { // disable automatic install otherwise mac updater will permanently wait on mocked electron's native updater to receive update (mocked server can't install) updater.autoInstallOnAppQuit = false From cb40ebe7a537a3b04a002316f8a2bc69cdbcb344 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Mon, 26 Feb 2024 08:12:12 -0800 Subject: [PATCH 16/18] adding check and logging in case initial update.zip file isn't present, in which case differential download cannot proceed, so we avoid a confusing error message with this check. --- .changeset/breezy-pans-shop.md | 5 +++++ packages/electron-updater/src/MacUpdater.ts | 16 ++++++++++++---- test/src/updater/differentialUpdateTest.ts | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 .changeset/breezy-pans-shop.md diff --git a/.changeset/breezy-pans-shop.md b/.changeset/breezy-pans-shop.md new file mode 100644 index 00000000000..57bd35eadc3 --- /dev/null +++ b/.changeset/breezy-pans-shop.md @@ -0,0 +1,5 @@ +--- +"electron-updater": minor +--- + +feat: adding differential downloader for updates on macOS diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index 87630be867e..9816d54e616 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -1,5 +1,5 @@ import { AllPublishOptions, newError, safeStringifyJson } from "builder-util-runtime" -import { stat } from "fs-extra" +import { pathExistsSync, stat } from "fs-extra" import { createReadStream, copyFileSync } from "fs" import * as path from "path" import { createServer, IncomingMessage, Server, ServerResponse } from "http" @@ -93,17 +93,25 @@ export class MacUpdater extends AppUpdater { } const provider = downloadUpdateOptions.updateInfoAndProvider.provider - const CURRENT_MAC_APP_ZIP_FILE_NAME = "updater.zip" + const CURRENT_MAC_APP_ZIP_FILE_NAME = "update.zip" return this.executeDownload({ fileExtension: "zip", fileInfo: zipFileInfo, downloadUpdateOptions, task: async (destinationFile, downloadOptions) => { - if (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME)) { + const cachedFile = path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_MAC_APP_ZIP_FILE_NAME) + const canDifferentialDownload = () => { + if (!pathExistsSync(cachedFile)) { + log.info("Unable to locate previous update.zip for differential download (is this first install?), falling back to full download") + return false + } + return !downloadUpdateOptions.disableDifferentialDownload + } + if (canDifferentialDownload() && (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME))) { await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions) } - copyFileSync(destinationFile, path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_MAC_APP_ZIP_FILE_NAME)) + copyFileSync(destinationFile, cachedFile) }, done: event => this.updateDownloaded(zipFileInfo, event), }) diff --git a/test/src/updater/differentialUpdateTest.ts b/test/src/updater/differentialUpdateTest.ts index e7042f3a916..1699dcb8f55 100644 --- a/test/src/updater/differentialUpdateTest.ts +++ b/test/src/updater/differentialUpdateTest.ts @@ -131,7 +131,7 @@ async function testMac(arch: Arch) { const oldDir = outDirs[0] const blockmap = `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip.blockmap` await move(path.join(oldDir, blockmap), path.join(outDirs[1], blockmap)) - await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "updater.zip")) + await move(path.join(oldDir, `Test App ßW-${OLD_VERSION_NUMBER}${getArchSuffix(arch)}-mac.zip`), path.join(getTestUpdaterCacheDir(oldDir), testAppCacheDirName, "update.zip")) await testBlockMap(outDirs[0], outDirs[1], MacUpdater, Platform.MAC, arch, "Test App ßW") } finally { From 9e808475721c74e0292ad5698c0812ce67e8d443 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 3 Mar 2024 21:34:26 +0800 Subject: [PATCH 17/18] fix temp file --- packages/electron-updater/src/MacUpdater.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index 9816d54e616..3d707480426 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -94,15 +94,16 @@ export class MacUpdater extends AppUpdater { const provider = downloadUpdateOptions.updateInfoAndProvider.provider const CURRENT_MAC_APP_ZIP_FILE_NAME = "update.zip" + let cachedUpdateFile: string = "" return this.executeDownload({ fileExtension: "zip", fileInfo: zipFileInfo, downloadUpdateOptions, task: async (destinationFile, downloadOptions) => { - const cachedFile = path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_MAC_APP_ZIP_FILE_NAME) + cachedUpdateFile = path.join(this.downloadedUpdateHelper!.cacheDir, CURRENT_MAC_APP_ZIP_FILE_NAME) const canDifferentialDownload = () => { - if (!pathExistsSync(cachedFile)) { + if (!pathExistsSync(cachedUpdateFile)) { log.info("Unable to locate previous update.zip for differential download (is this first install?), falling back to full download") return false } @@ -111,9 +112,15 @@ export class MacUpdater extends AppUpdater { if (canDifferentialDownload() && (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME))) { await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions) } - copyFileSync(destinationFile, cachedFile) }, - done: event => this.updateDownloaded(zipFileInfo, event), + done: event => { + try { + copyFileSync(event.downloadedFile, cachedUpdateFile) + } catch (error: any) { + this._logger.error(`Unable to copy file for caching: ${error.message}`) + } + return this.updateDownloaded(zipFileInfo, event) + }, }) } From f52a95b52b9650e16f6fdfedd4b2501160def416 Mon Sep 17 00:00:00 2001 From: "payne.fu" Date: Mon, 4 Mar 2024 10:11:10 +0800 Subject: [PATCH 18/18] fix full download issue --- packages/electron-updater/src/MacUpdater.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index 3d707480426..a989acb68f9 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -109,7 +109,12 @@ export class MacUpdater extends AppUpdater { } return !downloadUpdateOptions.disableDifferentialDownload } - if (canDifferentialDownload() && (await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME))) { + let differentialDownloadFailed = true + if (canDifferentialDownload()) { + differentialDownloadFailed = await this.differentialDownloadInstaller(zipFileInfo, downloadUpdateOptions, destinationFile, provider, CURRENT_MAC_APP_ZIP_FILE_NAME) + } + + if (differentialDownloadFailed) { await this.httpExecutor.download(zipFileInfo.url, destinationFile, downloadOptions) } },