Skip to content

Commit 834434c

Browse files
committed
feat(nsis): per-machine installer automatically removes per-user installation
Closes #621, #672
1 parent 682ddde commit 834434c

File tree

10 files changed

+91
-112
lines changed

10 files changed

+91
-112
lines changed

.idea/dictionaries/develar.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker/nsis-linux.sh

Lines changed: 0 additions & 8 deletions
This file was deleted.

docker/nsis.sh

Lines changed: 0 additions & 50 deletions
This file was deleted.

docs/Options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ See [NSIS target notes](https://github.com/electron-userland/electron-builder/wi
142142
| 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.
143143
| 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).
144144
| 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).
145+
| 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).
145146

146147
<a name="LinuxBuildOptions"></a>
147148
### `.build.linux`

src/metadata.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,11 @@ export interface NsisOptions {
416416
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).
417417
*/
418418
readonly script?: string | null
419+
420+
/*
421+
* Hex LCID, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396).
422+
*/
423+
readonly language?: string | null
419424
}
420425

421426
/*

src/targets/nsis.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import semver = require("semver")
1414
//noinspection JSUnusedLocalSymbols
1515
const __awaiter = require("../util/awaiter")
1616

17-
const NSIS_VERSION = "3.0.0"
17+
const NSIS_VERSION = "3.0.1"
1818
//noinspection SpellCheckingInspection
19-
const NSIS_SHA2 = "7741089f3ca13de879f87836156ef785eab49844cacbeeabaeaefd1ade325ee7"
19+
const NSIS_SHA2 = "23280f66c07c923da6f29a3c318377720c8ecd7af4de3755256d1ecf60d07f74"
2020

2121
//noinspection SpellCheckingInspection
2222
const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3"
@@ -119,13 +119,14 @@ export default class NsisTarget extends Target {
119119
// Error: invalid VIProductVersion format, should be X.X.X.X
120120
// so, we must strip beta
121121
const parsedVersion = new semver.SemVer(appInfo.version)
122+
const localeId = this.options.language || "1033"
122123
const versionKey = [
123-
`ProductName "${appInfo.productName}"`,
124-
`ProductVersion "${appInfo.version}"`,
125-
`CompanyName "${appInfo.companyName}"`,
126-
`LegalCopyright "${appInfo.copyright}"`,
127-
`FileDescription "${appInfo.description}"`,
128-
`FileVersion "${appInfo.buildVersion}"`,
124+
`/LANG=${localeId} ProductName "${appInfo.productName}"`,
125+
`/LANG=${localeId} ProductVersion "${appInfo.version}"`,
126+
`/LANG=${localeId} CompanyName "${appInfo.companyName}"`,
127+
`/LANG=${localeId} LegalCopyright "${appInfo.copyright}"`,
128+
`/LANG=${localeId} FileDescription "${appInfo.description}"`,
129+
`/LANG=${localeId} FileVersion "${appInfo.buildVersion}"`,
129130
]
130131
use(this.packager.platformSpecificBuildOptions.legalTrademarks, it => versionKey.push(`LegalTrademarks "${it}"`))
131132

templates/nsis/install.nsh

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ ${if} $R0 != ""
2323
ExecWait "$R0 /S /KEEP_APP_DATA"
2424
${endif}
2525

26+
${if} $installMode == "all"
27+
# remove per-user installation
28+
ReadRegStr $R0 HKEY_CURRENT_USER "${UNINSTALL_REGISTRY_KEY}" UninstallString
29+
${if} $R0 != ""
30+
ExecWait "$R0 /S /KEEP_APP_DATA"
31+
${endif}
32+
${endif}
33+
2634
RMDir /r $INSTDIR
2735
SetOutPath $INSTDIR
2836

@@ -68,12 +76,13 @@ WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}"
6876
!insertmacro customInstall
6977
!endif
7078

71-
${IfNot} ${Silent}
72-
!ifdef ONE_CLICK
73-
# otherwise app window will be in backround
74-
HideWindow
75-
!ifdef RUN_AFTER_FINISH
79+
!ifdef ONE_CLICK
80+
!ifdef RUN_AFTER_FINISH
81+
${IfNot} ${Silent}
82+
# otherwise app window will be in backround
83+
HideWindow
7684
Call StartApp
77-
!endif
85+
${EndIf}
7886
!endif
79-
${EndIf}
87+
Quit
88+
!endif

templates/nsis/oneClick.nsh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
!endif
1212
!endif
1313

14-
AutoCloseWindow true
1514
!insertmacro MUI_PAGE_INSTFILES
1615
!ifdef BUILD_UNINSTALLER
1716
!insertmacro MUI_UNPAGE_INSTFILES

test/src/helpers/wine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class WineManager {
3636
this.env = await this.winePreparePromise
3737
}
3838

39-
async exec(...args: Array<string>) {
39+
exec(...args: Array<string>) {
4040
return exec("wine", args, {env: this.env})
4141
}
4242

test/src/nsisTest.ts

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@ import { WineManager, diff } from "./helpers/wine"
1414
const __awaiter = require("out/util/awaiter")
1515

1616
const nsisTarget = Platform.WINDOWS.createTarget(["nsis"])
17-
test("one-click", app({targets: nsisTarget}, {useTempDir: true, signed: true}))
17+
18+
test("one-click", app({
19+
targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32),
20+
devMetadata: {
21+
build: {
22+
// wine creates incorrect filenames and registry entries for unicode, so, we use ASCII
23+
productName: "TestApp",
24+
}
25+
}
26+
}, {
27+
useTempDir: true,
28+
signed: true,
29+
packed: (projectDir, outDir) => {
30+
return doTest(outDir, true)
31+
}
32+
}))
1833

1934
test.ifDevOrLinuxCi("perMachine, no run after finish", app({
2035
targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32),
@@ -39,58 +54,64 @@ test.ifDevOrLinuxCi("perMachine, no run after finish", app({
3954
let headerIconPath = path.join(projectDir, "build", "foo.ico")
4055
return copy(getTestAsset("headerIcon.ico"), headerIconPath)
4156
},
42-
packed: async (projectDir, outDir) => {
43-
if (process.env.CI != null) {
44-
return
45-
}
57+
packed: (projectDir, outDir) => {
58+
return doTest(outDir, false)
59+
},
60+
}))
4661

47-
const wine = new WineManager()
48-
await wine.prepare()
49-
const driveC = path.join(wine.wineDir, "drive_c")
50-
const driveCWindows = path.join(wine.wineDir, "drive_c", "windows")
51-
const perUserTempDir = path.join(wine.userDir, "Temp")
52-
const walkFilter = (it: string) => {
53-
return it !== driveCWindows && it !== perUserTempDir
54-
}
62+
async function doTest(outDir: string, perUser: boolean) {
63+
if (process.env.DO_WINE !== "true") {
64+
return BluebirdPromise.resolve()
65+
}
5566

56-
function listFiles() {
57-
return walk(driveC, null, walkFilter)
58-
}
67+
const wine = new WineManager()
68+
await wine.prepare()
69+
const driveC = path.join(wine.wineDir, "drive_c")
70+
const driveCWindows = path.join(wine.wineDir, "drive_c", "windows")
71+
const perUserTempDir = path.join(wine.userDir, "Temp")
72+
const walkFilter = (it: string) => {
73+
return it !== driveCWindows && it !== perUserTempDir
74+
}
5975

60-
let fsBefore = await listFiles()
76+
function listFiles() {
77+
return walk(driveC, null, walkFilter)
78+
}
6179

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

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

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

71-
let fsChanges = diff(fsBefore, fsAfter, driveC)
72-
assertThat(fsChanges.added).isEqualTo(nsisPerMachineInstall)
73-
assertThat(fsChanges.deleted).isEqualTo([])
90+
let fsAfter = await listFiles()
7491

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

82-
fsChanges = diff(fsBefore, fsAfter, driveC)
83-
assertThat(fsChanges.added).isEqualTo([])
84-
assertThat(fsChanges.deleted).isEqualTo([])
85-
},
86-
}))
96+
// run installer again to test uninstall
97+
const appDataFile = path.join(wine.userDir, "Application Data", "TestApp", "doNotDeleteMe")
98+
await outputFile(appDataFile, "app data must be not removed")
99+
fsBefore = await listFiles()
100+
await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe", "/S"))
101+
fsAfter = await listFiles()
102+
103+
fsChanges = diff(fsBefore, fsAfter, driveC)
104+
assertThat(fsChanges.added).isEqualTo([])
105+
assertThat(fsChanges.deleted).isEqualTo([])
106+
}
87107

88108
test.ifNotCiOsx("boring", app({
89109
targets: nsisTarget,
90110
devMetadata: {
91111
build: {
92112
nsis: {
93113
oneClick: false,
114+
language: "1031",
94115
}
95116
}
96117
}

0 commit comments

Comments
 (0)