From d5c48bed7bd748e3b6dbc78ddb4ec1ea9dfbbe43 Mon Sep 17 00:00:00 2001 From: develar Date: Thu, 9 Feb 2017 08:16:54 +0100 Subject: [PATCH] WIP(nsis): NSIS web installer #1207 --- docs/Auto Update.md | 18 +- docs/Publishing Artifacts.md | 3 +- .../electron-builder-http/src/httpExecutor.ts | 4 +- .../src/publishOptions.ts | 15 +- .../src/options/winOptions.ts | 9 + .../src/publish/PublishManager.ts | 27 ++- packages/electron-builder/src/targets/nsis.ts | 206 +++++++++++------- packages/electron-builder/src/winPackager.ts | 8 +- .../templates/nsis/installSection.nsh | 93 +++++--- packages/electron-updater/src/AppUpdater.ts | 19 +- .../windows/__snapshots__/nsisTest.js.snap | 53 ++++- test/src/helpers/runTests.ts | 6 +- test/src/mac/macArchiveTest.ts | 3 +- test/src/windows/nsisTest.ts | 11 + 14 files changed, 330 insertions(+), 145 deletions(-) diff --git a/docs/Auto Update.md b/docs/Auto Update.md index 5891c6f39ac..c2816b8c02f 100644 --- a/docs/Auto Update.md +++ b/docs/Auto Update.md @@ -69,7 +69,7 @@ Emitted when checking if an update has started. #### Event: `update-available` -* `info` [UpdateInfo](#updateinfo) for generic and github providers. [VersionInfo](#versioninfo) for Bintray provider. +* `info` [UpdateInfo](#updateinfo) (for generic and github providers) | [VersionInfo](#versioninfo) (for Bintray provider) Emitted when there is an available update. The update is downloaded automatically if `autoDownload` is `true`. @@ -77,10 +77,10 @@ Emitted when there is an available update. The update is downloaded automaticall Emitted when there is no available update. -* `info` [UpdateInfo](#updateinfo) for generic and github providers. [VersionInfo](#versioninfo) for Bintray provider. +* `info` [UpdateInfo](#updateinfo) (for generic and github providers) | [VersionInfo](#versioninfo) (for Bintray provider) #### Event: `download-progress` -* `progressObj` - it's object with properties: +* `progress` ProgressInfo * `bytesPerSecond` * `percent` * `total` @@ -90,7 +90,7 @@ Emitted on progress. Only supported over Windows build, since `Squirrel.Mac` [do #### Event: `update-downloaded` -* `info` [UpdateInfo](#updateinfo) for generic and github providers. [VersionInfo](#versioninfo) for Bintray provider. +* `info` [UpdateInfo](#updateinfo) — for generic and github providers. [VersionInfo](#versioninfo) for Bintray provider. Emitted when an update has been downloaded. @@ -100,7 +100,7 @@ The `autoUpdater` object has the following methods: #### `autoUpdater.setFeedURL(options)` -* `options` GenericServerOptions | BintrayOptions | GithubOptions | string — if you want to override configuration in the `app-update.yml`. +* `options` GenericServerOptions | S3Options | BintrayOptions | GithubOptions | string — if you want to override configuration in the `app-update.yml`. Sets the `options`. If value is `string`, `GenericServerOptions` will be set with value as `url`. @@ -118,13 +118,13 @@ This is different from the normal quit event sequence. ### VersionInfo -* `version` The version. +* `version` string — The version. ### UpdateInfo Extends [VersionInfo](#versioninfo). -* `releaseDate` The release date. -* `releaseName?` The release name. -* `releaseNotes?` The release notes. +* `releaseDate` string — The release date. +* `releaseName` string (optional) — The release name. +* `releaseNotes` string (optional) — The release notes. diff --git a/docs/Publishing Artifacts.md b/docs/Publishing Artifacts.md index 1d4a2c6abd6..47abcbb5cd5 100644 --- a/docs/Publishing Artifacts.md +++ b/docs/Publishing Artifacts.md @@ -118,7 +118,8 @@ Amazon S3 — `https` must be used, so, if you use direct Amazon S3 endpoints, f | Name | Description | --- | --- -| bucket | The bucket name. +| **bucket** | The bucket name. +| path | The directory path. Defaults to `/`. | channel | The channel. Defaults to `latest`. | acl | The ACL. Defaults to `public-read`. | storageClass | The type of storage to use for the object. One of `STANDARD`, `REDUCED_REDUNDANCY`, `STANDARD_IA`. Defaults to `STANDARD`. diff --git a/packages/electron-builder-http/src/httpExecutor.ts b/packages/electron-builder-http/src/httpExecutor.ts index 25567127abd..d68cb749b39 100644 --- a/packages/electron-builder-http/src/httpExecutor.ts +++ b/packages/electron-builder-http/src/httpExecutor.ts @@ -4,7 +4,7 @@ import { createWriteStream } from "fs-extra-p" import { RequestOptions } from "http" import { parse as parseUrl } from "url" import _debug from "debug" -import { ProgressCallbackTransform } from "./ProgressCallbackTransform" +import { ProgressCallbackTransform, ProgressInfo } from "./ProgressCallbackTransform" import { safeLoad } from "js-yaml" import { EventEmitter } from "events" import { Socket } from "net" @@ -30,7 +30,7 @@ export interface DownloadOptions { readonly cancellationToken: CancellationToken - onProgress?(progress: any): void + onProgress?(progress: ProgressInfo): void } export class HttpExecutorHolder { diff --git a/packages/electron-builder-http/src/publishOptions.ts b/packages/electron-builder-http/src/publishOptions.ts index 6c37d67f116..0d4a7d830bb 100644 --- a/packages/electron-builder-http/src/publishOptions.ts +++ b/packages/electron-builder-http/src/publishOptions.ts @@ -53,7 +53,12 @@ export interface S3Options extends PublishConfiguration { /* The bucket name. */ - bucket?: string + bucket: string + + /* + The directory path. Defaults to `/`. + */ + path?: string | null /** The channel. Defaults to `latest`. @@ -73,6 +78,14 @@ export interface S3Options extends PublishConfiguration { secret?: string | null } +export function s3Url(options: S3Options) { + let url = `https://s3.amazonaws.com/${options.bucket}` + if (options.path != null) { + url += `/${options.path}` + } + return url +} + export interface VersionInfo { readonly version: string } diff --git a/packages/electron-builder/src/options/winOptions.ts b/packages/electron-builder/src/options/winOptions.ts index 68f9eb3fb9e..e56cc105f08 100644 --- a/packages/electron-builder/src/options/winOptions.ts +++ b/packages/electron-builder/src/options/winOptions.ts @@ -154,6 +154,15 @@ export interface NsisOptions { readonly artifactName?: string | null } +export interface NsisWebOptions extends NsisOptions { + /* + The application package download URL. Optional — by default computed using publish configuration. + + URL like `https://example.com/download/latest` allows web installer to be version independent (installer will download latest application package). + */ + readonly appPackageUrl?: string | null +} + /* ### `squirrelWindows` diff --git a/packages/electron-builder/src/publish/PublishManager.ts b/packages/electron-builder/src/publish/PublishManager.ts index 0a2c1ac3f3e..99af515a324 100644 --- a/packages/electron-builder/src/publish/PublishManager.ts +++ b/packages/electron-builder/src/publish/PublishManager.ts @@ -1,7 +1,7 @@ import BluebirdPromise from "bluebird-lst-c" import { createHash } from "crypto" import { Arch, Platform } from "electron-builder-core" -import { GenericServerOptions, GithubOptions, PublishConfiguration, S3Options, UpdateInfo, VersionInfo } from "electron-builder-http/out/publishOptions" +import { GenericServerOptions, GithubOptions, PublishConfiguration, S3Options, UpdateInfo, VersionInfo, s3Url } from "electron-builder-http/out/publishOptions" import { asArray, debug, isEmptyOrSpaces } from "electron-builder-util" import { log } from "electron-builder-util/out/log" import { throwError } from "electron-builder-util/out/promise" @@ -172,7 +172,7 @@ export class PublishManager implements PublishContext { } } -async function getPublishConfigsForUpdateInfo(packager: PlatformPackager, publishConfigs: Array | null): Promise | null> { +export async function getPublishConfigsForUpdateInfo(packager: PlatformPackager, publishConfigs: Array | null): Promise | null> { if (publishConfigs === null) { return null } @@ -281,19 +281,30 @@ export function createPublisher(context: PublishContext, version: string, publis return null } -function computeDownloadUrl(publishConfig: PublishConfiguration, fileName: string, version: string, macros: Macros) { +export function computeDownloadUrl(publishConfig: PublishConfiguration, fileName: string | null, version: string, macros: Macros) { if (publishConfig.provider === "generic") { - const baseUrl = url.parse(expandPattern((publishConfig).url, macros)) + const baseUrlString = expandPattern((publishConfig).url, macros) + if (fileName == null) { + return baseUrlString + } + + const baseUrl = url.parse(baseUrlString) return url.format(Object.assign({}, baseUrl, {pathname: path.posix.resolve(baseUrl.pathname || "/", encodeURI(fileName))})) } - else if (publishConfig.provider === "s3") { - const bucket = (publishConfig).bucket - return `https://s3.amazonaws.com/${bucket}/${fileName}` + + let baseUrl + if (publishConfig.provider === "s3") { + baseUrl = s3Url((publishConfig)) } else { const gh = publishConfig - return `https://github.com${`/${gh.owner}/${gh.repo}/releases`}/download/v${version}/${encodeURI(fileName)}` + baseUrl = `https://github.com${`/${gh.owner}/${gh.repo}/releases`}/download/v${version}` + } + + if (fileName == null) { + return baseUrl } + return `${baseUrl}/${encodeURI(fileName)}` } function expandPattern(pattern: string, macros: Macros): string { diff --git a/packages/electron-builder/src/targets/nsis.ts b/packages/electron-builder/src/targets/nsis.ts index 1ba2b13333f..1d5fdf102a2 100644 --- a/packages/electron-builder/src/targets/nsis.ts +++ b/packages/electron-builder/src/targets/nsis.ts @@ -8,14 +8,15 @@ import { normalizeExt } from "../platformPackager" import { archive } from "./archive" import { subTask, log, warn } from "electron-builder-util/out/log" import { unlink, readFile } from "fs-extra-p" -import { NsisOptions } from "../options/winOptions" -import { Target, Arch } from "electron-builder-core" +import { NsisOptions, NsisWebOptions } from "../options/winOptions" +import { Target, Arch, Platform } from "electron-builder-core" import sanitizeFileName from "sanitize-filename" import { copyFile } from "electron-builder-util/out/fs" +import { computeDownloadUrl, getPublishConfigs, getPublishConfigsForUpdateInfo } from "../publish/PublishManager" -const NSIS_VERSION = "3.0.1.5" +const NSIS_VERSION = "3.0.1.6" //noinspection SpellCheckingInspection -const NSIS_SHA2 = "cf996b4209f302c1f6b379a6b2090ad0d51a360daf24d1828eeceafd1617a976" +const NSIS_SHA2 = "4bd85f3a54311fd55814ec87fbcb0ab9c64b3dea4179c891e7748df8748f87c8" //noinspection SpellCheckingInspection const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3" @@ -25,14 +26,20 @@ const nsisPathPromise = getBinFromBintray("nsis", NSIS_VERSION, NSIS_SHA2) const USE_NSIS_BUILT_IN_COMPRESSOR = false export default class NsisTarget extends Target { - private readonly options: NsisOptions = this.packager.config.nsis || Object.create(null) + private readonly options: NsisOptions private archs: Map = new Map() private readonly nsisTemplatesDir = path.join(__dirname, "..", "..", "templates", "nsis") - constructor(private packager: WinPackager, private outDir: string) { - super("nsis") + constructor(private packager: WinPackager, private outDir: string, targetName: string) { + super(targetName) + + let options = this.packager.config.nsis || Object.create(null) + if (targetName !== "nsis") { + options = Object.assign(options, (this.packager.config)[targetName]) + } + this.options = options const deps = packager.info.metadata.dependencies if (deps != null && deps["electron-squirrel-startup"] != null) { @@ -106,10 +113,118 @@ export default class NsisTarget extends Target { await BluebirdPromise.map(this.archs.keys(), async arch => { const file = await this.doBuild(this.archs.get(arch)!, arch) defines[arch === Arch.x64 ? "APP_64" : "APP_32"] = file - filesToDelete.push(file) + defines[(arch === Arch.x64 ? "APP_64" : "APP_32") + "_NAME"] = path.basename(file) + + if (this.isWebInstaller) { + packager.dispatchArtifactCreated(file, this) + } + else { + filesToDelete.push(file) + } }) } + await this.configureDefines(oneClick, defines) + + if (this.isWebInstaller) { + let appPackageUrl = (options).appPackageUrl + if (appPackageUrl == null) { + const publishConfigs = await getPublishConfigsForUpdateInfo(packager, await getPublishConfigs(packager, this.options, false)) + if (publishConfigs == null || publishConfigs.length === 0) { + throw new Error("Cannot compute app package download URL") + } + + computeDownloadUrl(publishConfigs[0], null, packager.appInfo.version, { + os: Platform.WINDOWS.buildConfigurationKey, + arch: Arch[Arch.x64] + }) + + defines.APP_PACKAGE_URL_IS_INCOMLETE = null + } + + defines.APP_PACKAGE_URL = appPackageUrl + } + + const commands: any = { + OutFile: `"${installerPath}"`, + VIProductVersion: appInfo.versionInWeirdWindowsForm, + VIAddVersionKey: this.computeVersionKey(), + Unicode: true, + } + + if (packager.config.compression === "store") { + commands.SetCompress = "off" + } + else { + commands.SetCompressor = "lzma" + if (!this.isWebInstaller) { + defines.COMPRESS = "auto" + } + } + + debug(defines) + debug(commands) + + if (packager.packagerOptions.effectiveOptionComputed != null && await packager.packagerOptions.effectiveOptionComputed([defines, commands])) { + return + } + + await subTask(`Executing makensis — installer`, this.executeMakensis(defines, commands, true, await this.computeScript(defines, commands, installerPath))) + await packager.sign(installerPath) + + packager.dispatchArtifactCreated(installerPath, this, `${packager.appInfo.name}-Setup-${version}.exe`) + } + + private get isWebInstaller(): boolean { + return this.name === "nsis-web" + } + + private async computeScript(defines: any, commands: any, installerPath: string) { + const packager = this.packager + const customScriptPath = await packager.getResource(this.options.script, "installer.nsi") + const script = await readFile(customScriptPath || path.join(this.nsisTemplatesDir, "installer.nsi"), "utf8") + + if (customScriptPath != null) { + log("Custom NSIS script is used - uninstaller is not signed by electron-builder") + return script + } + + const uninstallerPath = await + packager.getTempFile("uninstaller.exe") + const isWin = process.platform === "win32" + defines.BUILD_UNINSTALLER = null + defines.UNINSTALLER_OUT_FILE = isWin ? uninstallerPath : path.win32.join("Z:", uninstallerPath) + await subTask(`Executing makensis — uninstaller`, this.executeMakensis(defines, commands, false, script)) + await exec(isWin ? installerPath : "wine", isWin ? [] : [installerPath]) + await packager.sign(uninstallerPath) + + delete defines.BUILD_UNINSTALLER + // platform-specific path, not wine + defines.UNINSTALLER_OUT_FILE = uninstallerPath + return script + } + + private computeVersionKey() { + // Error: invalid VIProductVersion format, should be X.X.X.X + // so, we must strip beta + const localeId = this.options.language || "1033" + const appInfo = this.packager.appInfo + const versionKey = [ + `/LANG=${localeId} ProductName "${appInfo.productName}"`, + `/LANG=${localeId} ProductVersion "${appInfo.version}"`, + `/LANG=${localeId} CompanyName "${appInfo.companyName}"`, + `/LANG=${localeId} LegalCopyright "${appInfo.copyright}"`, + `/LANG=${localeId} FileDescription "${appInfo.description}"`, + `/LANG=${localeId} FileVersion "${appInfo.buildVersion}"`, + ] + use(this.packager.platformSpecificBuildOptions.legalTrademarks, it => versionKey.push(`/LANG=${localeId} LegalTrademarks "${it}"`)) + return versionKey + } + + private async configureDefines(oneClick: boolean, defines: any) { + const packager = this.packager + const options = this.options + const installerHeader = oneClick ? null : await packager.getResource(options.installerHeader, "installerHeader.bmp") if (installerHeader != null) { defines.MUI_HEADERIMAGE = null @@ -146,87 +261,26 @@ export default class NsisTarget extends Target { defines.allowToChangeInstallationDirectory = null } - // Error: invalid VIProductVersion format, should be X.X.X.X - // so, we must strip beta - const localeId = options.language || "1033" - const versionKey = [ - `/LANG=${localeId} ProductName "${appInfo.productName}"`, - `/LANG=${localeId} ProductVersion "${appInfo.version}"`, - `/LANG=${localeId} CompanyName "${appInfo.companyName}"`, - `/LANG=${localeId} LegalCopyright "${appInfo.copyright}"`, - `/LANG=${localeId} FileDescription "${appInfo.description}"`, - `/LANG=${localeId} FileVersion "${appInfo.buildVersion}"`, - ] - use(packager.platformSpecificBuildOptions.legalTrademarks, it => versionKey.push(`/LANG=${localeId} LegalTrademarks "${it}"`)) - - const commands: any = { - OutFile: `"${installerPath}"`, - VIProductVersion: appInfo.versionInWeirdWindowsForm, - VIAddVersionKey: versionKey, - Unicode: true, - } - - if (packager.config.compression === "store") { - commands.SetCompress = "off" - } - else { - commands.SetCompressor = "lzma" - defines.COMPRESS = "auto" - } + if (!this.isWebInstaller && defines.APP_BUILD_DIR == null) { + if (options.useZip) { + defines.ZIP_COMPRESSION = null + } - if (this.options.useZip) { - defines.ZIP_COMPRESSION = null + defines.COMPRESSION_METHOD = options.useZip ? "zip" : "7z" } - defines.COMPRESSION_METHOD = this.options.useZip ? "zip" : "7z" - if (oneClick) { defines.ONE_CLICK = null } if (options.menuCategory != null) { - const menu = sanitizeFileName(options.menuCategory === true ? appInfo.companyName : options.menuCategory) + const menu = sanitizeFileName(options.menuCategory === true ? packager.appInfo.companyName : options.menuCategory) if (!isEmptyOrSpaces(menu)) { defines.MENU_FILENAME = menu } } - debug(defines) - debug(commands) - - if (packager.packagerOptions.effectiveOptionComputed != null && await packager.packagerOptions.effectiveOptionComputed([defines, commands])) { - return - } - - const licenseFile = await packager.getResource(options.license, "license.rtf", "license.txt") - if (licenseFile != null) { - defines.LICENSE_FILE = licenseFile - } - - const customScriptPath = await packager.getResource(options.script, "installer.nsi") - const script = await readFile(customScriptPath || path.join(this.nsisTemplatesDir, "installer.nsi"), "utf8") - - if (customScriptPath == null) { - const uninstallerPath = await packager.getTempFile("uninstaller.exe") - const isWin = process.platform === "win32" - defines.BUILD_UNINSTALLER = null - defines.UNINSTALLER_OUT_FILE = isWin ? uninstallerPath : path.win32.join("Z:", uninstallerPath) - await subTask(`Executing makensis — uninstaller`, this.executeMakensis(defines, commands, false, script)) - await exec(isWin ? installerPath : "wine", isWin ? [] : [installerPath]) - await packager.sign(uninstallerPath) - - delete defines.BUILD_UNINSTALLER - // platform-specific path, not wine - defines.UNINSTALLER_OUT_FILE = uninstallerPath - } - else { - log("Custom NSIS script is used - uninstaller is not signed by electron-builder") - } - - await subTask(`Executing makensis — installer`, this.executeMakensis(defines, commands, true, script)) - await packager.sign(installerPath) - - packager.dispatchArtifactCreated(installerPath, this, `${packager.appInfo.name}-Setup-${version}.exe`) + use(await packager.getResource(options.license, "license.rtf", "license.txt"), it => defines.LICENSE_FILE = it) } private async executeMakensis(defines: any, commands: any, isInstaller: boolean, originalScript: string) { diff --git a/packages/electron-builder/src/winPackager.ts b/packages/electron-builder/src/winPackager.ts index 9da367359d9..b10530cac59 100644 --- a/packages/electron-builder/src/winPackager.ts +++ b/packages/electron-builder/src/winPackager.ts @@ -78,6 +78,7 @@ export class WinPackager extends PlatformPackager { const targetClass: typeof NsisTarget | typeof AppXTarget | null = (() => { switch (name) { case "nsis": + case "nsis-web": return require("./targets/nsis").default case "squirrel": @@ -85,7 +86,7 @@ export class WinPackager extends PlatformPackager { return require("electron-builder-squirrel-windows").default } catch (e) { - throw new Error(`Since electron-builder 11, module electron-builder-squirrel-windows must be installed in addition to build Squirrel.Windows: ${e.stack || e}`) + throw new Error(`Module electron-builder-squirrel-windows must be installed in addition to build Squirrel.Windows: ${e.stack || e}`) } case "appx": @@ -96,7 +97,7 @@ export class WinPackager extends PlatformPackager { } })() - mapper(name, outDir => targetClass === null ? createCommonTarget(name, outDir, this) : new targetClass(this, outDir)) + mapper(name, outDir => targetClass === null ? createCommonTarget(name, outDir, this) : new (targetClass)(this, outDir, name)) } } @@ -129,8 +130,7 @@ export class WinPackager extends PlatformPackager { async sign(file: string) { const cscInfo = await this.cscInfo if (cscInfo == null) { - const forceCodeSigningPlatform = this.platformSpecificBuildOptions.forceCodeSigning - if (forceCodeSigningPlatform == null ? this.config.forceCodeSigning : forceCodeSigningPlatform) { + if (this.forceCodeSigning) { throw new Error(`App is not signed and "forceCodeSigning" is set to true, please ensure that code signing configuration is correct, please see https://github.com/electron-userland/electron-builder/wiki/Code-Signing`) } diff --git a/packages/electron-builder/templates/nsis/installSection.nsh b/packages/electron-builder/templates/nsis/installSection.nsh index 61a1752f001..ef49a5c34e3 100644 --- a/packages/electron-builder/templates/nsis/installSection.nsh +++ b/packages/electron-builder/templates/nsis/installSection.nsh @@ -75,6 +75,41 @@ WriteRegDWORD SHCTX "${UNINSTALL_REGISTRY_KEY}" "EstimatedSize" "$0" !macroend +!macro doExtractEmbeddedAppPackage ARCH + !ifdef ZIP_COMPRESSION + nsisunz::Unzip "$PLUGINSDIR\app-${ARCH}.zip" "$INSTDIR" + !else + Nsis7z::Extract "$PLUGINSDIR\app-${ARCH}.7z" + !endif +!macroend + +!macro extractEmbeddedAppPackage + !ifdef COMPRESS + SetCompress off + !endif + + !ifdef APP_32 + File /oname=$PLUGINSDIR\app-32.${COMPRESSION_METHOD} "${APP_32}" + !endif + !ifdef APP_64 + File /oname=$PLUGINSDIR\app-64.${COMPRESSION_METHOD} "${APP_64}" + !endif + + !ifdef COMPRESS + SetCompress "${COMPRESS}" + !endif + + !ifdef APP_64 + ${if} ${RunningX64} + !insertmacro doExtractEmbeddedAppPackage "64" + ${else} + !insertmacro doExtractEmbeddedAppPackage "32" + ${endif} + !else + !insertmacro doExtractEmbeddedAppPackage "32" + !endif +!macroend + InitPluginsDir !ifdef HEADER_ICO @@ -111,41 +146,37 @@ SetOutPath $INSTDIR !ifdef APP_BUILD_DIR File /r "${APP_BUILD_DIR}/*.*" !else - !ifdef COMPRESS - SetCompress off - !endif - - !ifdef APP_32 - File /oname=$PLUGINSDIR\app-32.${COMPRESSION_METHOD} "${APP_32}" - !endif - !ifdef APP_64 - File /oname=$PLUGINSDIR\app-64.${COMPRESSION_METHOD} "${APP_64}" - !endif - - !ifdef COMPRESS - SetCompress "${COMPRESS}" - !endif - - !ifdef APP_64 - ${if} ${RunningX64} - !ifdef ZIP_COMPRESSION - nsisunz::Unzip "$PLUGINSDIR\app-64.zip" "$INSTDIR" + !ifdef APP_PACKAGE_URL + StrCpy $0 "${APP_PACKAGE_URL}" + !ifdef APP_PACKAGE_URL_IS_INCOMLETE + !ifdef APP_64_NAME + !ifdef APP_32_NAME + ${if} ${RunningX64} + StrCpy $0 "$0/${APP_64_NAME}" + ${else} + StrCpy $0 "$0/${APP_32_NAME}" + ${endif} + !else + StrCpy $0 "$0/${APP_64_NAME}" + !endif !else - Nsis7z::Extract "$PLUGINSDIR\app-64.7z" - !endif - ${else} - !ifdef ZIP_COMPRESSION - nsisunz::Unzip "$PLUGINSDIR\app-32.zip" "$INSTDIR" - !else - Nsis7z::Extract "$PLUGINSDIR\app-32.7z" + StrCpy $0 "$0/${APP_32_NAME}" !endif + !endif + + download: + inetc::get /RESUME "$0" "$PLUGINSDIR\package.7z" + pop $0 + ${if} $0 == "Cancelled" + quit + ${elseif} $0 != "OK" + messagebox MB_RETRYCANCEL|MB_ICONEXCLAMATION "Unable to download application package (status: $0).$\r$\n$\r$\nPlease check you Internet connection and retry." IDRETRY download + quit ${endif} + + Nsis7z::Extract "$PLUGINSDIR\package.7z" !else - !ifdef ZIP_COMPRESSION - nsisunz::Unzip "$PLUGINSDIR\app-32.zip" "$INSTDIR" - !else - Nsis7z::Extract "$PLUGINSDIR\app-32.7z" - !endif + !insertmacro extractEmbeddedAppPackage !endif !endif diff --git a/packages/electron-updater/src/AppUpdater.ts b/packages/electron-updater/src/AppUpdater.ts index d04fea107b4..1e565ed3054 100644 --- a/packages/electron-updater/src/AppUpdater.ts +++ b/packages/electron-updater/src/AppUpdater.ts @@ -5,7 +5,7 @@ import { RequestHeaders, executorHolder } from "electron-builder-http" import { Provider, UpdateCheckResult, FileInfo, UpdaterSignal } from "./api" import { BintrayProvider } from "./BintrayProvider" import BluebirdPromise from "bluebird-lst-c" -import { BintrayOptions, PublishConfiguration, GithubOptions, S3Options, GenericServerOptions, VersionInfo } from "electron-builder-http/out/publishOptions" +import { BintrayOptions, PublishConfiguration, GithubOptions, S3Options, GenericServerOptions, VersionInfo, s3Url } from "electron-builder-http/out/publishOptions" import { readFile } from "fs-extra-p" import { safeLoad } from "js-yaml" import { GenericProvider } from "./GenericProvider" @@ -239,7 +239,7 @@ export abstract class AppUpdater extends EventEmitter { } } -function createClient(data: string | PublishConfiguration | BintrayOptions | GithubOptions | S3Options) { +function createClient(data: string | PublishConfiguration) { if (typeof data === "string") { throw new Error("Please pass PublishConfiguration object") } @@ -248,15 +248,22 @@ function createClient(data: string | PublishConfiguration | BintrayOptions | Git switch (provider) { case "github": return new GitHubProvider(data) - case "s3": - return new GenericProvider({ - url: `https://s3.amazonaws.com/${(data).bucket || ""}`, - channel: (data).channel || "" + + case "s3": { + const s3 = data + return new GenericProvider({ + provider: "generic", + url: s3Url(s3), + channel: s3.channel || "" }) + } + case "generic": return new GenericProvider(data) + case "bintray": return new BintrayProvider(data) + default: throw new Error(`Unsupported provider: ${provider}`) } diff --git a/test/out/windows/__snapshots__/nsisTest.js.snap b/test/out/windows/__snapshots__/nsisTest.js.snap index d8ef9b9b6f8..3105371740b 100644 --- a/test/out/windows/__snapshots__/nsisTest.js.snap +++ b/test/out/windows/__snapshots__/nsisTest.js.snap @@ -5,6 +5,12 @@ Array [ `; exports[`test allowToChangeInstallationDirectory 2`] = ` +Array [ + "test-custom-inst-dir-Setup-1.1.0.exe", +] +`; + +exports[`test allowToChangeInstallationDirectory 3`] = ` Object { "owner": "foo", "provider": "github", @@ -12,7 +18,7 @@ Object { } `; -exports[`test allowToChangeInstallationDirectory 3`] = ` +exports[`test allowToChangeInstallationDirectory 4`] = ` Object { "githubArtifactName": "test-custom-inst-dir-Setup-1.1.0.exe", "path": "Test Custom Installation Dir Setup 1.1.0.exe", @@ -26,18 +32,36 @@ Array [ ] `; +exports[`test custom include 2`] = ` +Array [ + "TestApp-Setup-1.1.0.exe", +] +`; + exports[`test custom script 1`] = ` Array [ "Test App ßW Setup 1.1.0.exe", ] `; +exports[`test custom script 2`] = ` +Array [ + "TestApp-Setup-1.1.0.exe", +] +`; + exports[`test menuCategory 1`] = ` Array [ "Test Menu Category CustomName 1.1.0.exe", ] `; +exports[`test menuCategory 2`] = ` +Array [ + "test-menu-category-Setup-1.1.0.exe", +] +`; + exports[`test one-click 1`] = ` Array [ "Test App ßW Setup 1.1.0.exe", @@ -45,6 +69,12 @@ Array [ `; exports[`test one-click 2`] = ` +Array [ + "TestApp-Setup-1.1.0.exe", +] +`; + +exports[`test one-click 3`] = ` Object { "owner": "actperepo", "package": "TestApp", @@ -60,16 +90,35 @@ Array [ `; exports[`test perMachine, no run after finish 2`] = ` +Array [ + "TestApp-Setup-1.1.0.exe", +] +`; + +exports[`test perMachine, no run after finish 3`] = ` Object { "provider": "generic", "url": "https://develar.s3.amazonaws.com/test/win/x64", } `; -exports[`test perMachine, no run after finish 3`] = ` +exports[`test perMachine, no run after finish 4`] = ` Object { "githubArtifactName": "TestApp-Setup-1.1.0.exe", "path": "TestApp Setup 1.1.0.exe", "version": "1.1.0", } `; + +exports[`test web installer 1`] = ` +Array [ + "Test App ßW Setup 1.1.0.exe", + "TestApp-1.1.0-x64.nsis.7z", +] +`; + +exports[`test web installer 2`] = ` +Array [ + "TestApp-Setup-1.1.0.exe", +] +`; diff --git a/test/src/helpers/runTests.ts b/test/src/helpers/runTests.ts index 6cc21b31b7e..e50825d0925 100755 --- a/test/src/helpers/runTests.ts +++ b/test/src/helpers/runTests.ts @@ -83,8 +83,8 @@ async function runTests() { else if (!isEmptyOrSpaces(process.env.CIRCLE_NODE_INDEX)) { const circleNodeIndex = parseInt(process.env.CIRCLE_NODE_INDEX, 10) if (circleNodeIndex === 0) { - args.push("debTest", "linuxArchiveTest") - args.push("nsisUpdaterTest") + args.push("debTest") + args.push("fpmTest") } else if (circleNodeIndex === 1) { args.push("BuildTest.js", "extraMetadataTest.js", "mainEntryTest.js", "globTest.js", "filesTest.js", "ignoreTest.js") @@ -92,7 +92,7 @@ async function runTests() { args.push(...baseForLinuxTests) } else if (circleNodeIndex === 2) { - args.push("snapTest", "fpmTest") + args.push("snapTest", "nsisUpdaterTest", "linuxArchiveTest") } else { args.push("windows.*") diff --git a/test/src/mac/macArchiveTest.ts b/test/src/mac/macArchiveTest.ts index 0a719183688..f9a0c9a3a0c 100644 --- a/test/src/mac/macArchiveTest.ts +++ b/test/src/mac/macArchiveTest.ts @@ -15,10 +15,9 @@ test("only zip", createMacTargetTest(["zip"])); test("tar.gz", createMacTargetTest(["tar.gz"])) const it = process.env.CSC_KEY_PASSWORD == null ? test.skip : test.ifMac - it("pkg", createMacTargetTest(["pkg"])) -it.ifMac("pkg scripts", app({ +test.ifMac("pkg scripts", app({ targets: Platform.MAC.createTarget("pkg"), }, { useTempDir: true, diff --git a/test/src/windows/nsisTest.ts b/test/src/windows/nsisTest.ts index d5e1346b333..016f45c5801 100644 --- a/test/src/windows/nsisTest.ts +++ b/test/src/windows/nsisTest.ts @@ -225,4 +225,15 @@ test.ifDevOrLinuxCi("file associations only perMachine", appThrows(/Please set p } ], }, +})) + +test.ifNotCiMac("web installer", app({ + targets: Platform.WINDOWS.createTarget(["nsis-web"], Arch.x64), + config: { + publish: { + provider: "s3", + bucket: "develar", + path: "test", + } + } })) \ No newline at end of file