Skip to content

Commit

Permalink
feat(nsis): per-machine installer automatically removes per-user inst…
Browse files Browse the repository at this point in the history
…allation

Closes #621, #672
  • Loading branch information
develar committed Aug 20, 2016
1 parent 682ddde commit 834434c
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 112 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/develar.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 0 additions & 8 deletions docker/nsis-linux.sh

This file was deleted.

50 changes: 0 additions & 50 deletions docker/nsis.sh

This file was deleted.

1 change: 1 addition & 0 deletions docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ See [NSIS target notes](https://github.com/electron-userland/electron-builder/wi
| installerHeaderIcon | <a name="NsisOptions-installerHeaderIcon"></a>*one-click installer only.* The path to header icon (above the progress bar), relative to the project directory. Defaults to `build/installerHeaderIcon.ico` or application icon.
| include | <a name="NsisOptions-include"></a>The path to NSIS include script to customize installer. Defaults to `build/installer.nsh`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script).
| script | <a name="NsisOptions-script"></a>The path to NSIS script to customize installer. Defaults to `build/installer.nsi`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script).
| language | <a name="NsisOptions-language"></a>* Hex LCID, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396).

<a name="LinuxBuildOptions"></a>
### `.build.linux`
Expand Down
5 changes: 5 additions & 0 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,11 @@ export interface NsisOptions {
The path to NSIS script to customize installer. Defaults to `build/installer.nsi`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script).
*/
readonly script?: string | null

/*
* Hex LCID, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396).
*/
readonly language?: string | null
}

/*
Expand Down
17 changes: 9 additions & 8 deletions src/targets/nsis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import semver = require("semver")
//noinspection JSUnusedLocalSymbols
const __awaiter = require("../util/awaiter")

const NSIS_VERSION = "3.0.0"
const NSIS_VERSION = "3.0.1"
//noinspection SpellCheckingInspection
const NSIS_SHA2 = "7741089f3ca13de879f87836156ef785eab49844cacbeeabaeaefd1ade325ee7"
const NSIS_SHA2 = "23280f66c07c923da6f29a3c318377720c8ecd7af4de3755256d1ecf60d07f74"

//noinspection SpellCheckingInspection
const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3"
Expand Down Expand Up @@ -119,13 +119,14 @@ export default class NsisTarget extends Target {
// Error: invalid VIProductVersion format, should be X.X.X.X
// so, we must strip beta
const parsedVersion = new semver.SemVer(appInfo.version)
const localeId = this.options.language || "1033"
const versionKey = [
`ProductName "${appInfo.productName}"`,
`ProductVersion "${appInfo.version}"`,
`CompanyName "${appInfo.companyName}"`,
`LegalCopyright "${appInfo.copyright}"`,
`FileDescription "${appInfo.description}"`,
`FileVersion "${appInfo.buildVersion}"`,
`/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(`LegalTrademarks "${it}"`))

Expand Down
23 changes: 16 additions & 7 deletions templates/nsis/install.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ ${if} $R0 != ""
ExecWait "$R0 /S /KEEP_APP_DATA"
${endif}

${if} $installMode == "all"
# remove per-user installation
ReadRegStr $R0 HKEY_CURRENT_USER "${UNINSTALL_REGISTRY_KEY}" UninstallString
${if} $R0 != ""
ExecWait "$R0 /S /KEEP_APP_DATA"
${endif}
${endif}

RMDir /r $INSTDIR
SetOutPath $INSTDIR

Expand Down Expand Up @@ -68,12 +76,13 @@ WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}"
!insertmacro customInstall
!endif

${IfNot} ${Silent}
!ifdef ONE_CLICK
# otherwise app window will be in backround
HideWindow
!ifdef RUN_AFTER_FINISH
!ifdef ONE_CLICK
!ifdef RUN_AFTER_FINISH
${IfNot} ${Silent}
# otherwise app window will be in backround
HideWindow
Call StartApp
!endif
${EndIf}
!endif
${EndIf}
Quit
!endif
1 change: 0 additions & 1 deletion templates/nsis/oneClick.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
!endif
!endif

AutoCloseWindow true
!insertmacro MUI_PAGE_INSTFILES
!ifdef BUILD_UNINSTALLER
!insertmacro MUI_UNPAGE_INSTFILES
Expand Down
2 changes: 1 addition & 1 deletion test/src/helpers/wine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class WineManager {
this.env = await this.winePreparePromise
}

async exec(...args: Array<string>) {
exec(...args: Array<string>) {
return exec("wine", args, {env: this.env})
}

Expand Down
95 changes: 58 additions & 37 deletions test/src/nsisTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,22 @@ import { WineManager, diff } from "./helpers/wine"
const __awaiter = require("out/util/awaiter")

const nsisTarget = Platform.WINDOWS.createTarget(["nsis"])
test("one-click", app({targets: nsisTarget}, {useTempDir: true, signed: true}))

test("one-click", app({
targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32),
devMetadata: {
build: {
// wine creates incorrect filenames and registry entries for unicode, so, we use ASCII
productName: "TestApp",
}
}
}, {
useTempDir: true,
signed: true,
packed: (projectDir, outDir) => {
return doTest(outDir, true)
}
}))

test.ifDevOrLinuxCi("perMachine, no run after finish", app({
targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32),
Expand All @@ -39,58 +54,64 @@ test.ifDevOrLinuxCi("perMachine, no run after finish", app({
let headerIconPath = path.join(projectDir, "build", "foo.ico")
return copy(getTestAsset("headerIcon.ico"), headerIconPath)
},
packed: async (projectDir, outDir) => {
if (process.env.CI != null) {
return
}
packed: (projectDir, outDir) => {
return doTest(outDir, false)
},
}))

const wine = new WineManager()
await wine.prepare()
const driveC = path.join(wine.wineDir, "drive_c")
const driveCWindows = path.join(wine.wineDir, "drive_c", "windows")
const perUserTempDir = path.join(wine.userDir, "Temp")
const walkFilter = (it: string) => {
return it !== driveCWindows && it !== perUserTempDir
}
async function doTest(outDir: string, perUser: boolean) {
if (process.env.DO_WINE !== "true") {
return BluebirdPromise.resolve()
}

function listFiles() {
return walk(driveC, null, walkFilter)
}
const wine = new WineManager()
await wine.prepare()
const driveC = path.join(wine.wineDir, "drive_c")
const driveCWindows = path.join(wine.wineDir, "drive_c", "windows")
const perUserTempDir = path.join(wine.userDir, "Temp")
const walkFilter = (it: string) => {
return it !== driveCWindows && it !== perUserTempDir
}

let fsBefore = await listFiles()
function listFiles() {
return walk(driveC, null, walkFilter)
}

await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe"), "/S")
let fsBefore = await listFiles()

const appAsar = path.join(driveC, "Program Files", "TestApp", "resources", "app.asar")
assertThat(JSON.parse(extractFile(appAsar, "package.json").toString())).hasProperties({
name: "TestApp"
})
await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe"), "/S")

let fsAfter = await listFiles()
const instDir = perUser ? path.join(wine.userDir, "Local Settings", "Application Data", "Programs") : path.join(driveC, "Program Files")
const appAsar = path.join(instDir, "TestApp", "resources", "app.asar")
assertThat(JSON.parse(extractFile(appAsar, "package.json").toString())).hasProperties({
name: "TestApp"
})

let fsChanges = diff(fsBefore, fsAfter, driveC)
assertThat(fsChanges.added).isEqualTo(nsisPerMachineInstall)
assertThat(fsChanges.deleted).isEqualTo([])
let fsAfter = await listFiles()

// run installer again to test uninstall
const appDataFile = path.join(wine.userDir, "Application Data", "TestApp", "doNotDeleteMe")
await outputFile(appDataFile, "app data must be not removed")
fsBefore = await listFiles()
await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe"))
fsAfter = await listFiles()
let fsChanges = diff(fsBefore, fsAfter, driveC)
assertThat(fsChanges.added).isEqualTo(nsisPerMachineInstall)
assertThat(fsChanges.deleted).isEqualTo([])

fsChanges = diff(fsBefore, fsAfter, driveC)
assertThat(fsChanges.added).isEqualTo([])
assertThat(fsChanges.deleted).isEqualTo([])
},
}))
// run installer again to test uninstall
const appDataFile = path.join(wine.userDir, "Application Data", "TestApp", "doNotDeleteMe")
await outputFile(appDataFile, "app data must be not removed")
fsBefore = await listFiles()
await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe", "/S"))
fsAfter = await listFiles()

fsChanges = diff(fsBefore, fsAfter, driveC)
assertThat(fsChanges.added).isEqualTo([])
assertThat(fsChanges.deleted).isEqualTo([])
}

test.ifNotCiOsx("boring", app({
targets: nsisTarget,
devMetadata: {
build: {
nsis: {
oneClick: false,
language: "1031",
}
}
}
Expand Down

0 comments on commit 834434c

Please sign in to comment.