From e50e3c8101ce029f6ad41dd0327f2638f78d9f2d Mon Sep 17 00:00:00 2001 From: develar Date: Sun, 19 Jun 2016 14:27:05 +0200 Subject: [PATCH] feat: finish NSIS installer look and feel * use excellent SpiderBanner for one-click installer * remove welcome screen from boring installer and finish page from boring uninstaller * move some defines to nsh to simplify customization (i.e. less magic) --- .travis.yml | 11 ------- docker/nsis.sh | 13 ++++++-- docs/Options.md | 10 +++--- package.json | 10 +++--- src/metadata.ts | 10 +++--- src/targets/nsis.ts | 24 ++++---------- src/util/binDownload.ts | 14 +++++--- .../nsis/allowOnlyOneInstallerInstace.nsh | 2 +- templates/nsis/boring-installer.nsh | 6 +--- templates/nsis/common.nsh | 16 ++++++++++ templates/nsis/installer.nsi | 32 ++++++++++--------- test/src/winPackagerTest.ts | 13 ++++++-- 12 files changed, 87 insertions(+), 74 deletions(-) create mode 100644 templates/nsis/common.nsh diff --git a/.travis.yml b/.travis.yml index 2fb8e8714d5..73ab9ca57a4 100755 --- a/.travis.yml +++ b/.travis.yml @@ -15,17 +15,6 @@ cache: - node_modules - $HOME/.electron - $HOME/.cache/fpm - - /usr/local/Cellar/wine - - /usr/local/Cellar/mono - - /usr/local/Cellar/graphicsmagick - - /usr/local/Cellar/freetype - - /usr/local/Cellar/gmp - - /usr/local/Cellar/openssl - - /usr/local/Cellar/lzip - - /usr/local/Cellar/libtiff - - /usr/local/Cellar/libtool - - /usr/local/Cellar/libicns - - /usr/local/Cellar/dpkg before_install: - brew install --ignore-dependencies --force-bottle gnu-tar dpkg libicns graphicsmagick lzip freetype diff --git a/docker/nsis.sh b/docker/nsis.sh index e6b6335ee61..3d76d09d05f 100755 --- a/docker/nsis.sh +++ b/docker/nsis.sh @@ -5,6 +5,7 @@ rm -rf Docs rm -rf NSIS.chm rm -rf Examples rm -rf Plugins/x86-ansi +unlink makensisw.exe # nsProcess plugin curl -L http://nsis.sourceforge.net/mediawiki/images/1/18/NsProcess.zip > a.zip @@ -36,8 +37,14 @@ mv a/Plugins/x86-unicode/nsis7z.dll Plugins/x86-unicode/nsis7z.dll unlink a.zip rm -rf a +# http://nsis.sourceforge.net/SpiderBanner_plug-in +curl -L http://nsis.sourceforge.net/mediawiki/images/4/4c/SpiderBanner_plugin.zip > a.zip +7za x a.zip -oa +mv a/Plugins/x86-unicode/SpiderBanner.dll Plugins/x86-unicode/SpiderBanner.dll +unlink a.zip +rm -rf a + dir=${PWD##*/} -cd .. -rm -rf ${dir}.7z -7za a -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ${dir}.7z ${dir} +rm -rf ../${dir}.7z +7za a -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../${dir}.7z . diff --git a/docs/Options.md b/docs/Options.md index 8a154775c34..a43a746bf4a 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -94,11 +94,11 @@ MAS (Mac Application Store) specific options (in addition to `build.osx`). | Name | Description | --- | --- | target | Target package type: list of `squirrel`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`. Defaults to `squirrel`. -| iconUrl |

A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Electron icon.

Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.

-| loadingGif |

The path to a .gif file to display during install. build/install-spinner.gif will be used if exists (it is a recommended way to set) (otherwise [default](https://github.com/electron/windows-installer/blob/master/resources/install-spinner.gif)).

-| msi | Whether to create an MSI installer. Defaults to `false` (MSI is not created). -| remoteReleases | A URL to your existing updates. If given, these will be downloaded to create delta updates. -| remoteToken | Authentication token for remote updates +| iconUrl |

*Squirrel.Windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Electron icon.

Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.

+| loadingGif |

*Squirrel.Windows-only.* The path to a .gif file to display during install. build/install-spinner.gif will be used if exists (it is a recommended way to set) (otherwise [default](https://github.com/electron/windows-installer/blob/master/resources/install-spinner.gif)).

+| msi | *Squirrel.Windows-only.* Whether to create an MSI installer. Defaults to `false` (MSI is not created). +| remoteReleases | *Squirrel.Windows-only.* A URL to your existing updates. If given, these will be downloaded to create delta updates. +| remoteToken | *Squirrel.Windows-only.* Authentication token for remote updates | signingHashAlgorithms | Array of signing algorithms used. Defaults to `['sha1', 'sha256']` diff --git a/package.json b/package.json index cf352c65f83..2d674612b10 100644 --- a/package.json +++ b/package.json @@ -60,20 +60,20 @@ "7zip-bin": "^1.0.5", "ansi-escapes": "^1.4.0", "asar": "^0.11.0", - "bluebird": "^3.4.0", + "bluebird": "^3.4.1", "chalk": "^1.1.3", "cli-cursor": "^1.0.2", "debug": "^2.2.0", "deep-assign": "^2.0.0", "electron-osx-sign-tf": "0.6.0", "electron-packager-tf": "~7.4.0", - "electron-winstaller-fixed": "~2.10.1", + "electron-winstaller-fixed": "~2.10.2", "fs-extra-p": "^1.0.3", "hosted-git-info": "^2.1.5", "image-size": "^0.5.0", "lodash.template": "^4.2.5", "mime": "^1.3.4", - "minimatch": "^3.0.0", + "minimatch": "^3.0.2", "pretty-ms": "^2.1.0", "progress": "^1.1.8", "progress-stream": "^1.2.0", @@ -103,7 +103,7 @@ "@types/progress": "^1.1.21-alpha", "@types/semver": "^4.3.20-alpha", "@types/source-map-support": "^0.2.21-alpha", - "ava-tf": "^0.15", + "ava-tf": "^0.15.3", "babel-plugin-array-includes": "^2.0.3", "babel-plugin-transform-es2015-destructuring": "^6.9.0", "babel-plugin-transform-es2015-parameters": "^6.9.0", @@ -114,7 +114,7 @@ "json8": "^0.9.0", "path-sort": "^0.1.0", "plist": "^1.2.0", - "pre-git": "^3.9.0", + "pre-git": "^3.9.1", "semantic-release": "^6.3.0", "should": "^9.0.2", "ts-babel": "^1.0.2", diff --git a/src/metadata.ts b/src/metadata.ts index 8a0b6bcaafc..f4fe50fcbf2 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -276,7 +276,7 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions { readonly target?: Array | null /* - A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Electron icon. + *Squirrel.Windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Electron icon. Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http. @@ -286,23 +286,23 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions { readonly iconUrl?: string | null /* - The path to a .gif file to display during install. `build/install-spinner.gif` will be used if exists (it is a recommended way to set) + *Squirrel.Windows-only.* The path to a .gif file to display during install. `build/install-spinner.gif` will be used if exists (it is a recommended way to set) (otherwise [default](https://github.com/electron/windows-installer/blob/master/resources/install-spinner.gif)). */ readonly loadingGif?: string | null /* - Whether to create an MSI installer. Defaults to `false` (MSI is not created). + *Squirrel.Windows-only.* Whether to create an MSI installer. Defaults to `false` (MSI is not created). */ readonly msi?: boolean /* - A URL to your existing updates. If given, these will be downloaded to create delta updates. + *Squirrel.Windows-only.* A URL to your existing updates. If given, these will be downloaded to create delta updates. */ readonly remoteReleases?: string | null /* - Authentication token for remote updates + *Squirrel.Windows-only.* Authentication token for remote updates */ readonly remoteToken?: string | null diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index c4588e3637d..47d8979434e 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -14,12 +14,13 @@ import semver = require("semver") //noinspection JSUnusedLocalSymbols const __awaiter = require("../awaiter") -const NSIS_SHA2 = "9ab9b92197c97fc910b506fa85225f7f41a80914fecadb9ae4aa496e7c2cc9a8" +const NSIS_VERSION = "nsis-3.0.0-rc.1.2" +const NSIS_SHA2 = "d96f714ba552a5ebccf2593ed3fee1b072b67e7bfd1b90d66a5eb0cd3ca41d16" //noinspection SpellCheckingInspection const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3" -const nsisPathPromise = getBin("nsis", `nsis-3.0rc1`, `https://dl.bintray.com/electron-userland/bin/nsis-3.0.0-rc.1.1.7z`, NSIS_SHA2) +const nsisPathPromise = getBin("nsis", NSIS_VERSION, `https://dl.bintray.com/electron-userland/bin/${NSIS_VERSION}.7z`, NSIS_SHA2) export default class NsisTarget { private readonly nsisOptions: NsisOptions @@ -43,6 +44,7 @@ export default class NsisTarget { const productName = appInfo.productName const defines: any = { APP_ID: appInfo.id, + APP_GUID: guid, PRODUCT_NAME: productName, INST_DIR_NAME: sanitizeFileName(productName), APP_DESCRIPTION: appInfo.description, @@ -53,13 +55,6 @@ export default class NsisTarget { MUI_UNICON: iconPath, COMPANY_NAME: appInfo.companyName, - APP_EXECUTABLE_FILENAME: `${appInfo.productName}.exe`, - UNINSTALL_FILENAME: `Uninstall ${productName}.exe`, - MULTIUSER_INSTALLMODE_INSTDIR: guid, - MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY: guid, - MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY: guid, - MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME: "UninstallString", - MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME: "InstallLocation", } if (this.nsisOptions.perMachine === true) { @@ -77,10 +72,7 @@ export default class NsisTarget { // so, we must strip beta const parsedVersion = new semver.SemVer(appInfo.version) const commands: any = { - FileBufSize: "64", - Name: `"${productName}"`, OutFile: `"${installerPath}"`, - Unicode: "true", // LoadLanguageFile: '"${NSISDIR}/Contrib/Language files/English.nlf"', VIProductVersion: `${parsedVersion.major}.${parsedVersion.minor}.${parsedVersion.patch}.${appInfo.buildNumber || "0"}`, // VIFileVersion: packager.appInfo.buildVersion, @@ -91,9 +83,6 @@ export default class NsisTarget { `FileDescription "${appInfo.description}"`, `FileVersion "${appInfo.buildVersion}"`, ], - ShowInstDetails: "nevershow", - ShowUninstDetails: "nevershow", - BrandingText: `" "`, } if (packager.devMetadata.build.compression === "store") { @@ -113,19 +102,18 @@ export default class NsisTarget { const oneClick = this.nsisOptions.oneClick !== false if (oneClick) { defines.ONE_CLICK = null - commands.AutoCloseWindow = "true" } debug(defines) debug(commands) - await subTask(`Executing makensis`, this.executeMakensis(defines, commands)) + await subTask(`Executing makensis`, NsisTarget.executeMakensis(defines, commands)) await packager.sign(installerPath) this.packager.dispatchArtifactCreated(installerPath, `${appInfo.name}-Setup-${version}${archSuffix}.exe`) } - private async executeMakensis(defines: any, commands: any) { + private static async executeMakensis(defines: any, commands: any) { const args: Array = [] for (let name of Object.keys(defines)) { const value = defines[name] diff --git a/src/util/binDownload.ts b/src/util/binDownload.ts index 464beae3e30..b9f84d46d43 100644 --- a/src/util/binDownload.ts +++ b/src/util/binDownload.ts @@ -17,14 +17,14 @@ export function downloadFpm(version: string, osAndArch: string): Promise .then(it => path.join(it, "fpm")) } -export function getBin(name: string, dirName: string, url: string, sha1?: string): Promise { +export function getBin(name: string, dirName: string, url: string, sha2?: string): Promise { let promise = versionToPromise.get(dirName) // if rejected, we will try to download again if (promise != null && !promise.isRejected()) { return promise } - promise = >doGetBin(name, dirName, url, sha1) + promise = >doGetBin(name, dirName, url, sha2) versionToPromise.set(dirName, promise) return promise } @@ -59,14 +59,20 @@ async function doGetBin(name: string, dirName: string, url: string, sha2?: strin stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], }) + const isOldMethod = sha2 == null + await BluebirdPromise.all([ - rename(path.join(tempUnpackDir, dirName), dirPath) + rename(isOldMethod ? path.join(tempUnpackDir, dirName) : tempUnpackDir, dirPath) .catch(e => { console.warn(`Cannot move downloaded ${name} into final location (another process downloaded faster?): ${e}`) }), unlink(archiveName), ]) - await remove(tempUnpackDir) + + if (isOldMethod) { + await remove(tempUnpackDir) + } + debug(`${name}} downloaded to ${dirPath}`) return dirPath } \ No newline at end of file diff --git a/templates/nsis/allowOnlyOneInstallerInstace.nsh b/templates/nsis/allowOnlyOneInstallerInstace.nsh index d606800479a..67a12281075 100644 --- a/templates/nsis/allowOnlyOneInstallerInstace.nsh +++ b/templates/nsis/allowOnlyOneInstallerInstace.nsh @@ -2,7 +2,7 @@ !macro ALLOW_ONLY_ONE_INSTALLER_INSTACE BringToFront !define /ifndef SYSTYPE_PTR p ; NSIS v3.0+ - System::Call 'kernel32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${APP_ID}")?e' + System::Call 'kernel32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${APP_GUID}")?e' Pop $0 IntCmpU $0 183 0 launch launch ; ERROR_ALREADY_EXISTS StrLen $0 "$(^SetupCaption)" diff --git a/templates/nsis/boring-installer.nsh b/templates/nsis/boring-installer.nsh index eaf3bf57c6c..1425e5be458 100644 --- a/templates/nsis/boring-installer.nsh +++ b/templates/nsis/boring-installer.nsh @@ -1,11 +1,8 @@ -BrandingText "${PRODUCT_NAME} ${VERSION}" - # http://nsis.sourceforge.net/Run_an_application_shortcut_after_an_install #!define MUI_FINISHPAGE_RUN_TEXT "Start ${PRODUCT_NAME}" !define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN_FUNCTION "StartApp" -!insertmacro MUI_PAGE_WELCOME !insertmacro MULTIUSER_PAGE_INSTALLMODE !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH @@ -14,5 +11,4 @@ BrandingText "${PRODUCT_NAME} ${VERSION}" # uninstall pages !insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES -!insertmacro MUI_UNPAGE_FINISH \ No newline at end of file +!insertmacro MUI_UNPAGE_INSTFILES \ No newline at end of file diff --git a/templates/nsis/common.nsh b/templates/nsis/common.nsh new file mode 100644 index 00000000000..249d1caf754 --- /dev/null +++ b/templates/nsis/common.nsh @@ -0,0 +1,16 @@ +BrandingText "${PRODUCT_NAME} ${VERSION}" +ShowInstDetails nevershow +ShowUninstDetails nevershow +FileBufSize 64 +Name "${PRODUCT_NAME}" +Unicode true + +!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "UninstallString" +!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "InstallLocation" + +!define MULTIUSER_INSTALLMODE_INSTDIR "${APP_GUID}" +!define MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY "${APP_GUID}" +!define MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY "${APP_GUID}" + +!define APP_EXECUTABLE_FILENAME "${PRODUCT_NAME}.exe" +!define UNINSTALL_FILENAME "Uninstall ${PRODUCT_NAME}.exe" \ No newline at end of file diff --git a/templates/nsis/installer.nsi b/templates/nsis/installer.nsi index 1158cc52be1..51c952a5ba9 100644 --- a/templates/nsis/installer.nsi +++ b/templates/nsis/installer.nsi @@ -1,3 +1,4 @@ +!include "common.nsh" !include "MUI2.nsh" !include "NsisMultiUser.nsh" !include "nsProcess.nsh" @@ -8,13 +9,12 @@ Function StartApp ExecShell "" "$SMPROGRAMS\${PRODUCT_NAME}.lnk" FunctionEnd -!ifndef ONE_CLICK - !include "boring-installer.nsh" -!endif - !ifdef ONE_CLICK + AutoCloseWindow true !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_INSTFILES +!else + !include "boring-installer.nsh" !endif Var startMenuLink @@ -23,7 +23,11 @@ Var desktopLink Function .onInit !insertmacro MULTIUSER_INIT !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE - !insertmacro CHECK_APP_RUNNING "install" + + InitPluginsDir + SetCompress off + File /oname=$PLUGINSDIR\app.7z "${APP_ARCHIVE}" + SetCompress "${COMPRESS}" FunctionEnd Function un.onInit @@ -34,18 +38,16 @@ FunctionEnd Section "install" SetDetailsPrint none - # delete the installed files - RMDir /r $INSTDIR + !ifdef ONE_CLICK + SpiderBanner::Show /MODERN + !endif - # define the path to which the installer should install - SetOutPath $INSTDIR + !insertmacro CHECK_APP_RUNNING "install" - SetCompress off - File /oname=app.7z "${APP_ARCHIVE}" - SetCompress "${COMPRESS}" + RMDir /r $INSTDIR + SetOutPath $INSTDIR - Nsis7z::Extract "app.7z" - Delete "app.7z" + Nsis7z::Extract "$PLUGINSDIR\app.7z" # <% if(fileAssociation){ %> # specify file association @@ -92,7 +94,7 @@ Section "un.install" !insertmacro MULTIUSER_RegistryRemoveInstallInfo !ifdef ONE_CLICK - # strange, AutoCloseWindow=true doesn't work for uninstaller, so, just quit + # strange, AutoCloseWindow true doesn't work for uninstaller, so, just quit Quit !endif SectionEnd \ No newline at end of file diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index debdc0b3519..6e0a9bdc8a9 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -27,13 +27,22 @@ test.ifNotCiOsx("win", () => assertPack("test-app-one", _signed({ test("nsis", () => assertPack("test-app-one", _signed({ targets: Platform.WINDOWS.createTarget(["nsis"]), + }), { + useTempDir: true, + } +)) + +test.ifNotCiOsx("nsis boring", () => assertPack("test-app-one", _signed({ + targets: Platform.WINDOWS.createTarget(["nsis"]), devMetadata: { build: { - productName: "Test App" + nsis: { + oneClick: false, + } } } }), { - useTempDir: true, + useTempDir: true, } ))