Skip to content

Commit

Permalink
feat: finish NSIS installer look and feel
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
develar committed Jun 19, 2016
1 parent e9e5024 commit e50e3c8
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 74 deletions.
11 changes: 0 additions & 11 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions docker/nsis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 .
10 changes: 5 additions & 5 deletions docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ MAS (Mac Application Store) specific options (in addition to `build.osx`).
| Name | Description
| --- | ---
| target | <a name="WinBuildOptions-target"></a>Target package type: list of `squirrel`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`. Defaults to `squirrel`.
| iconUrl | <a name="WinBuildOptions-iconUrl"></a><p>A URL to an ICO file to use as the application icon (displayed in Control Panel &gt; Programs and Features). Defaults to the Electron icon.</p> <p>Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.</p> <ul> <li>If you don’t plan to build windows installer, you can omit it.</li> <li>If your project repository is public on GitHub, it will be <code>https://github.com/${u}/${p}/blob/master/build/icon.ico?raw=true</code> by default.</li> </ul>
| loadingGif | <a name="WinBuildOptions-loadingGif"></a><p>The path to a .gif file to display during install. <code>build/install-spinner.gif</code> 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)).</p>
| msi | <a name="WinBuildOptions-msi"></a>Whether to create an MSI installer. Defaults to `false` (MSI is not created).
| remoteReleases | <a name="WinBuildOptions-remoteReleases"></a>A URL to your existing updates. If given, these will be downloaded to create delta updates.
| remoteToken | <a name="WinBuildOptions-remoteToken"></a>Authentication token for remote updates
| iconUrl | <a name="WinBuildOptions-iconUrl"></a><p>*Squirrel.Windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel &gt; Programs and Features). Defaults to the Electron icon.</p> <p>Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.</p> <ul> <li>If you don’t plan to build windows installer, you can omit it.</li> <li>If your project repository is public on GitHub, it will be <code>https://github.com/${u}/${p}/blob/master/build/icon.ico?raw=true</code> by default.</li> </ul>
| loadingGif | <a name="WinBuildOptions-loadingGif"></a><p>*Squirrel.Windows-only.* The path to a .gif file to display during install. <code>build/install-spinner.gif</code> 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)).</p>
| msi | <a name="WinBuildOptions-msi"></a>*Squirrel.Windows-only.* Whether to create an MSI installer. Defaults to `false` (MSI is not created).
| remoteReleases | <a name="WinBuildOptions-remoteReleases"></a>*Squirrel.Windows-only.* A URL to your existing updates. If given, these will be downloaded to create delta updates.
| remoteToken | <a name="WinBuildOptions-remoteToken"></a>*Squirrel.Windows-only.* Authentication token for remote updates
| signingHashAlgorithms | <a name="WinBuildOptions-signingHashAlgorithms"></a>Array of signing algorithms used. Defaults to `['sha1', 'sha256']`

<a name="NsisOptions"></a>
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions {
readonly target?: Array<string> | 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.
Expand All @@ -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

Expand Down
24 changes: 6 additions & 18 deletions src/targets/nsis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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") {
Expand All @@ -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<string> = []
for (let name of Object.keys(defines)) {
const value = defines[name]
Expand Down
14 changes: 10 additions & 4 deletions src/util/binDownload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export function downloadFpm(version: string, osAndArch: string): Promise<string>
.then(it => path.join(it, "fpm"))
}

export function getBin(name: string, dirName: string, url: string, sha1?: string): Promise<string> {
export function getBin(name: string, dirName: string, url: string, sha2?: string): Promise<string> {
let promise = versionToPromise.get(dirName)
// if rejected, we will try to download again
if (promise != null && !promise.isRejected()) {
return promise
}

promise = <BluebirdPromise<string>>doGetBin(name, dirName, url, sha1)
promise = <BluebirdPromise<string>>doGetBin(name, dirName, url, sha2)
versionToPromise.set(dirName, promise)
return promise
}
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion templates/nsis/allowOnlyOneInstallerInstace.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
6 changes: 1 addition & 5 deletions templates/nsis/boring-installer.nsh
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,5 +11,4 @@ BrandingText "${PRODUCT_NAME} ${VERSION}"

# uninstall pages
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_UNPAGE_INSTFILES
16 changes: 16 additions & 0 deletions templates/nsis/common.nsh
Original file line number Diff line number Diff line change
@@ -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"
32 changes: 17 additions & 15 deletions templates/nsis/installer.nsi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
!include "common.nsh"
!include "MUI2.nsh"
!include "NsisMultiUser.nsh"
!include "nsProcess.nsh"
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Loading

0 comments on commit e50e3c8

Please sign in to comment.