diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml
index eb129700c7e..1b86cb090f6 100644
--- a/.idea/dictionaries/develar.xml
+++ b/.idea/dictionaries/develar.xml
@@ -91,6 +91,7 @@
psmdcp
rcedit
readpass
+ regedit
rels
repos
rimraf
@@ -109,6 +110,7 @@
valuename
veyor
volid
+ winedlloverrides
winstaller
xamarin
xenial
diff --git a/docker/wine/Dockerfile b/docker/wine/Dockerfile
index ebfba72c5ca..6967f5c7796 100644
--- a/docker/wine/Dockerfile
+++ b/docker/wine/Dockerfile
@@ -11,5 +11,6 @@ apt-get install -y --no-install-recommends wine1.8 mono-devel ca-certificates-mo
apt-get clean && rm -rf /var/lib/apt/lists/*
ENV WINEDEBUG -all,err+all
+ENV WINEDLLOVERRIDES winemenubuilder.exe=d
RUN wineboot --init || true
\ No newline at end of file
diff --git a/src/asarUtil.ts b/src/asarUtil.ts
index 6f10830287f..f24c0dd807a 100644
--- a/src/asarUtil.ts
+++ b/src/asarUtil.ts
@@ -22,18 +22,20 @@ const MAX_FILE_REQUESTS = 32
const concurrency = {concurrency: MAX_FILE_REQUESTS}
const NODE_MODULES_PATTERN = path.sep + "node_modules" + path.sep
-function walk(dirPath: string, consumer: (file: string, stat: Stats) => void, filter: (file: string) => boolean, addRootToResult?: boolean): BluebirdPromise> {
+export function walk(dirPath: string, consumer?: (file: string, stat: Stats) => void, filter?: (file: string) => boolean, addRootToResult?: boolean): BluebirdPromise> {
return readdir(dirPath)
.then(names => {
return BluebirdPromise.map(names, name => {
const filePath = dirPath + path.sep + name
- if (!filter(filePath)) {
+ if (filter != null && !filter(filePath)) {
return null
}
return lstat(filePath)
.then((stat): any => {
- consumer(filePath, stat)
+ if (consumer != null) {
+ consumer(filePath, stat)
+ }
if (stat.isDirectory()) {
return walk(filePath, consumer, filter, true)
}
diff --git a/templates/nsis/install.nsh b/templates/nsis/install.nsh
index 6e11f041a16..d985a7d619b 100644
--- a/templates/nsis/install.nsh
+++ b/templates/nsis/install.nsh
@@ -1,3 +1,9 @@
+InitPluginsDir
+
+!ifdef HEADER_ICO
+ File /oname=$PLUGINSDIR\installerHeaderico.ico "${HEADER_ICO}"
+!endif
+
${IfNot} ${Silent}
SetDetailsPrint none
@@ -12,16 +18,23 @@ ${endif}
!insertmacro CHECK_APP_RUNNING "install"
-${if} $installMode == "all"
- ReadRegStr $R0 HKEY_LOCAL_MACHINE "${UNINSTALL_REGISTRY_KEY}" UninstallString
- ${if} $R0 != ""
- ExecWait "$R0 /S"
- ${endif}
+ReadRegStr $R0 SHCTX "${UNINSTALL_REGISTRY_KEY}" UninstallString
+${if} $R0 != ""
+ ExecWait "$R0 /S /KEEP_APP_DATA"
${endif}
RMDir /r $INSTDIR
SetOutPath $INSTDIR
+SetCompress off
+!ifdef APP_32
+ File /oname=$PLUGINSDIR\app-32.7z "${APP_32}"
+!endif
+!ifdef APP_64
+ File /oname=$PLUGINSDIR\app-64.7z "${APP_64}"
+!endif
+SetCompress "${COMPRESS}"
+
!ifdef APP_64
${If} ${RunningX64}
Nsis7z::Extract "$PLUGINSDIR\app-64.7z"
diff --git a/templates/nsis/installer.nsi b/templates/nsis/installer.nsi
index 6511c0beded..ad71b86fd5e 100644
--- a/templates/nsis/installer.nsi
+++ b/templates/nsis/installer.nsi
@@ -38,21 +38,6 @@ Function .onInit
${EndIf}
!endif
- InitPluginsDir
-
- SetCompress off
- !ifdef APP_32
- File /oname=$PLUGINSDIR\app-32.7z "${APP_32}"
- !endif
- !ifdef APP_64
- File /oname=$PLUGINSDIR\app-64.7z "${APP_64}"
- !endif
- SetCompress "${COMPRESS}"
-
- !ifdef HEADER_ICO
- File /oname=$PLUGINSDIR\installerHeaderico.ico "${HEADER_ICO}"
- !endif
-
!ifmacrodef customInit
!insertmacro customInit
!endif
diff --git a/templates/nsis/uninstaller.nsh b/templates/nsis/uninstaller.nsh
index 2fcba4000b8..10629f60627 100644
--- a/templates/nsis/uninstaller.nsh
+++ b/templates/nsis/uninstaller.nsh
@@ -20,8 +20,6 @@ Function un.onInit
FunctionEnd
Section "un.install"
- SetAutoClose true
-
!ifndef ONE_CLICK
# for boring installer we check it here to show progress
!insertmacro CHECK_APP_RUNNING "uninstall"
@@ -44,6 +42,7 @@ Section "un.install"
# delete the installed files
RMDir /r $INSTDIR
+ ClearErrors
${GetParameters} $R0
${GetOptions} $R0 "/KEEP_APP_DATA" $R1
${If} ${Errors}
@@ -63,4 +62,6 @@ Section "un.install"
!ifmacrodef customUnInstall
!insertmacro customUnInstall
!endif
+
+ Quit
SectionEnd
\ No newline at end of file
diff --git a/test/src/helpers/expectedContents.ts b/test/src/helpers/expectedContents.ts
index 805def2b891..3cbad92c465 100755
--- a/test/src/helpers/expectedContents.ts
+++ b/test/src/helpers/expectedContents.ts
@@ -1,3 +1,5 @@
+import pathSorter = require("path-sort")
+
//noinspection SpellCheckingInspection
export const expectedLinuxContents = ["/",
"/opt/",
@@ -81,4 +83,87 @@ export const expectedWinContents = [
"TestApp.nuspec",
"[Content_Types].xml",
"_rels/.rels"
-]
\ No newline at end of file
+]
+
+export const nsisPerMachineInstall = pathSorter([
+ "Program Files/TestApp",
+ "Program Files/TestApp/blink_image_resources_200_percent.pak",
+ "Program Files/TestApp/content_resources_200_percent.pak",
+ "Program Files/TestApp/content_shell.pak",
+ "Program Files/TestApp/d3dcompiler_47.dll",
+ "Program Files/TestApp/ffmpeg.dll",
+ "Program Files/TestApp/icudtl.dat",
+ "Program Files/TestApp/libEGL.dll",
+ "Program Files/TestApp/libGLESv2.dll",
+ "Program Files/TestApp/LICENSE",
+ "Program Files/TestApp/LICENSES.chromium.html",
+ "Program Files/TestApp/natives_blob.bin",
+ "Program Files/TestApp/node.dll",
+ "Program Files/TestApp/snapshot_blob.bin",
+ "Program Files/TestApp/TestApp.exe",
+ "Program Files/TestApp/ui_resources_200_percent.pak",
+ "Program Files/TestApp/Uninstall TestApp.exe",
+ "Program Files/TestApp/views_resources_200_percent.pak",
+ "Program Files/TestApp/xinput1_3.dll",
+ "Program Files/TestApp/resources",
+ "Program Files/TestApp/resources/app.asar",
+ "Program Files/TestApp/resources/electron.asar",
+ "Program Files/TestApp/resources/foo.ico",
+ "Program Files/TestApp/locales",
+ "Program Files/TestApp/locales/am.pak",
+ "Program Files/TestApp/locales/ar.pak",
+ "Program Files/TestApp/locales/bg.pak",
+ "Program Files/TestApp/locales/bn.pak",
+ "Program Files/TestApp/locales/ca.pak",
+ "Program Files/TestApp/locales/cs.pak",
+ "Program Files/TestApp/locales/da.pak",
+ "Program Files/TestApp/locales/de.pak",
+ "Program Files/TestApp/locales/el.pak",
+ "Program Files/TestApp/locales/en-GB.pak",
+ "Program Files/TestApp/locales/en-US.pak",
+ "Program Files/TestApp/locales/es-419.pak",
+ "Program Files/TestApp/locales/es.pak",
+ "Program Files/TestApp/locales/et.pak",
+ "Program Files/TestApp/locales/fa.pak",
+ "Program Files/TestApp/locales/fake-bidi.pak",
+ "Program Files/TestApp/locales/fi.pak",
+ "Program Files/TestApp/locales/fil.pak",
+ "Program Files/TestApp/locales/fr.pak",
+ "Program Files/TestApp/locales/gu.pak",
+ "Program Files/TestApp/locales/he.pak",
+ "Program Files/TestApp/locales/hi.pak",
+ "Program Files/TestApp/locales/hr.pak",
+ "Program Files/TestApp/locales/hu.pak",
+ "Program Files/TestApp/locales/id.pak",
+ "Program Files/TestApp/locales/it.pak",
+ "Program Files/TestApp/locales/ja.pak",
+ "Program Files/TestApp/locales/kn.pak",
+ "Program Files/TestApp/locales/ko.pak",
+ "Program Files/TestApp/locales/lt.pak",
+ "Program Files/TestApp/locales/lv.pak",
+ "Program Files/TestApp/locales/ml.pak",
+ "Program Files/TestApp/locales/mr.pak",
+ "Program Files/TestApp/locales/ms.pak",
+ "Program Files/TestApp/locales/nb.pak",
+ "Program Files/TestApp/locales/nl.pak",
+ "Program Files/TestApp/locales/pl.pak",
+ "Program Files/TestApp/locales/pt-BR.pak",
+ "Program Files/TestApp/locales/pt-PT.pak",
+ "Program Files/TestApp/locales/ro.pak",
+ "Program Files/TestApp/locales/ru.pak",
+ "Program Files/TestApp/locales/sk.pak",
+ "Program Files/TestApp/locales/sl.pak",
+ "Program Files/TestApp/locales/sr.pak",
+ "Program Files/TestApp/locales/sv.pak",
+ "Program Files/TestApp/locales/sw.pak",
+ "Program Files/TestApp/locales/ta.pak",
+ "Program Files/TestApp/locales/te.pak",
+ "Program Files/TestApp/locales/th.pak",
+ "Program Files/TestApp/locales/tr.pak",
+ "Program Files/TestApp/locales/uk.pak",
+ "Program Files/TestApp/locales/vi.pak",
+ "Program Files/TestApp/locales/zh-CN.pak",
+ "Program Files/TestApp/locales/zh-TW.pak",
+ "users/Public/Desktop/TestApp.lnk",
+ "users/Public/Start Menu/Programs/TestApp.lnk"
+])
\ No newline at end of file
diff --git a/test/src/helpers/fileAssert.ts b/test/src/helpers/fileAssert.ts
index 6ec043e8479..c0e43721957 100644
--- a/test/src/helpers/fileAssert.ts
+++ b/test/src/helpers/fileAssert.ts
@@ -89,7 +89,7 @@ class Assertions {
}
}
-function prettyDiff(actual: any, expected: any): string {
+export function prettyDiff(actual: any, expected: any): string {
const diffJson2 = diffJson(expected, actual)
const diff = diffJson2.map(part => {
if (part.added) {
diff --git a/test/src/helpers/wine.ts b/test/src/helpers/wine.ts
new file mode 100644
index 00000000000..5802419e369
--- /dev/null
+++ b/test/src/helpers/wine.ts
@@ -0,0 +1,115 @@
+import { exec } from "out/util/util"
+import { homedir } from "os"
+import { emptyDir, readFile, writeFile, ensureDir } from "fs-extra-p"
+import * as path from "path"
+import { Promise as BluebirdPromise } from "bluebird"
+import pathSorter = require("path-sort")
+import { unlinkIfExists } from "out/util/util"
+
+//noinspection JSUnusedLocalSymbols
+const __awaiter = require("out/util/awaiter")
+
+export class WineManager {
+ wineDir: string
+ private winePreparePromise: Promise | null
+
+ private env: any
+
+ userDir: string
+
+ async prepare() {
+ if (this.env != null) {
+ return
+ }
+
+ this.wineDir = path.join(homedir(), "wine-test")
+
+ const env = process.env
+ const user = env.SUDO_USER || env.LOGNAME || env.USER || env.LNAME || env.USERNAME || (env.HOME === "/root" ? "root" : null)
+ if (user == null) {
+ throw new Error(`Cannot determinate user name: ${JSON.stringify(env, null, 2)}`)
+ }
+
+ this.userDir = path.join(this.wineDir, "drive_c", "users", user)
+
+ this.winePreparePromise = this.prepareWine(this.wineDir)
+ this.env = await this.winePreparePromise
+ }
+
+ async exec(...args: Array) {
+ return exec("wine", args, {env: this.env})
+ }
+
+ async prepareWine(wineDir: string) {
+ await emptyDir(wineDir)
+ //noinspection SpellCheckingInspection
+ const env = Object.assign({}, process.env, {
+ WINEDLLOVERRIDES: "winemenubuilder.exe=d",
+ WINEPREFIX: wineDir
+ })
+
+ await exec("wineboot", ["--init"], {env: env})
+
+ // regedit often doesn't modify correctly
+ let systemReg = await readFile(path.join(wineDir, "system.reg"), "utf8")
+ systemReg = systemReg.replace('"CSDVersion"="Service Pack 3"', '"CSDVersion"=" "')
+ systemReg = systemReg.replace('"CurrentBuildNumber"="2600"', '"CurrentBuildNumber"="10240"')
+ systemReg = systemReg.replace('"CurrentVersion"="5.1"', '"CurrentVersion"="10.0"')
+ systemReg = systemReg.replace('"ProductName"="Microsoft Windows XP"', '"ProductName"="Microsoft Windows 10"')
+ systemReg = systemReg.replace('"CSDVersion"=dword:00000300', '"CSDVersion"=dword:00000000')
+ await writeFile(path.join(wineDir, "system.reg"), systemReg)
+
+ // remove links to host OS
+ const desktopDir = path.join(this.userDir, "Desktop")
+ await BluebirdPromise.all([
+ unlinkIfExists(desktopDir),
+ unlinkIfExists(path.join(this.userDir, "My Documents")),
+ unlinkIfExists(path.join(this.userDir, "My Music")),
+ unlinkIfExists(path.join(this.userDir, "My Pictures")),
+ unlinkIfExists(path.join(this.userDir, "My Videos")),
+ ])
+
+ await ensureDir(desktopDir)
+ return env
+ }
+}
+
+enum ChangeType {
+ ADDED, REMOVED, NO_CHANGE
+}
+
+export function diff(oldList: Array, newList: Array, rootDir: string) {
+ const delta: any = {
+ added: [],
+ deleted: [],
+ }
+ const deltaMap = new Map()
+ // const objHolder = new Set(oldList)
+ for (let item of oldList) {
+ deltaMap.set(item, ChangeType.REMOVED)
+ }
+
+ for (let item of newList) {
+ // objHolder.add(item)
+ const d = deltaMap.get(item)
+ if (d === ChangeType.REMOVED) {
+ deltaMap.set(item, ChangeType.NO_CHANGE)
+ }
+ else {
+ deltaMap.set(item, ChangeType.ADDED)
+ }
+ }
+
+ for (let [item, changeType] of deltaMap.entries()) {
+ if (changeType === ChangeType.REMOVED) {
+ delta.deleted.push(item.substring(rootDir.length + 1))
+ }
+ else if (changeType === ChangeType.ADDED) {
+ delta.added.push(item.substring(rootDir.length + 1))
+ }
+ }
+
+ delta.added = pathSorter(delta.added)
+ delta.deleted = pathSorter(delta.deleted)
+ return delta
+}
\ No newline at end of file
diff --git a/test/src/nsisTest.ts b/test/src/nsisTest.ts
index 72695c35640..0564891e7c3 100644
--- a/test/src/nsisTest.ts
+++ b/test/src/nsisTest.ts
@@ -1,10 +1,14 @@
import { Platform, Arch } from "out"
import test from "./helpers/avaEx"
import { assertPack, getTestAsset, app } from "./helpers/packTester"
-import { copy } from "fs-extra-p"
+import { copy, outputFile } from "fs-extra-p"
import * as path from "path"
import { Promise as BluebirdPromise } from "bluebird"
import { assertThat } from "./helpers/fileAssert"
+import { extractFile } from "asar-electron-builder"
+import { walk } from "out/asarUtil"
+import { nsisPerMachineInstall } from "./helpers/expectedContents"
+import { WineManager, diff } from "./helpers/wine"
//noinspection JSUnusedLocalSymbols
const __awaiter = require("out/util/awaiter")
@@ -13,9 +17,11 @@ const nsisTarget = Platform.WINDOWS.createTarget(["nsis"])
test("one-click", app({targets: nsisTarget}, {useTempDir: true, signed: true}))
test.ifDevOrLinuxCi("perMachine, no run after finish", app({
- targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32, Arch.x64),
+ targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32),
devMetadata: {
build: {
+ // wine creates incorrect filenames and registry entries for unicode, so, we use ASCII
+ productName: "TestApp",
fileAssociations: [
{
ext: "foo",
@@ -27,12 +33,56 @@ test.ifDevOrLinuxCi("perMachine, no run after finish", app({
runAfterFinish: false,
},
}
- }
+ },
}, {
projectDirCreated: projectDir => {
let headerIconPath = path.join(projectDir, "build", "foo.ico")
return copy(getTestAsset("headerIcon.ico"), headerIconPath)
},
+ packed: async (projectDir, outDir) => {
+ if (process.env.CI != null) {
+ return
+ }
+
+ 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
+ }
+
+ function listFiles() {
+ return walk(driveC, null, walkFilter)
+ }
+
+ let fsBefore = await listFiles()
+
+ await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe"), "/S")
+
+ const appAsar = path.join(driveC, "Program Files", "TestApp", "resources", "app.asar")
+ assertThat(JSON.parse(extractFile(appAsar, "package.json").toString())).hasProperties({
+ name: "TestApp"
+ })
+
+ let fsAfter = await listFiles()
+
+ let fsChanges = diff(fsBefore, fsAfter, driveC)
+ assertThat(fsChanges.added).isEqualTo(nsisPerMachineInstall)
+ 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"))
+ fsAfter = await listFiles()
+
+ fsChanges = diff(fsBefore, fsAfter, driveC)
+ assertThat(fsChanges.added).isEqualTo([])
+ assertThat(fsChanges.deleted).isEqualTo([])
+ },
}))
test.ifNotCiOsx("boring", app({
@@ -95,7 +145,7 @@ test.ifNotCiOsx("boring, MUI_HEADER", () => {
test.ifNotCiOsx("boring, MUI_HEADER as option", () => {
let installerHeaderPath: string | null = null
return assertPack("test-app-one", {
- targets: nsisTarget,
+ targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32, Arch.x64),
devMetadata: {
build: {
nsis: {