Skip to content

Commit 3f43c0a

Browse files
committed
feat(nsis): MUI_HEADERIMAGE #525
1 parent 26c89f0 commit 3f43c0a

File tree

9 files changed

+120
-20
lines changed

9 files changed

+120
-20
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
vendor/**/* filter=lfs diff=lfs merge=lfs -text
55
*.gif filter=lfs diff=lfs merge=lfs -text
66
*.keychain filter=lfs diff=lfs merge=lfs -text
7+
*.bmp filter=lfs diff=lfs merge=lfs -text

.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.

docs/Options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ See [NSIS target notes](https://github.com/electron-userland/electron-builder/wi
129129
| perMachine | <a name="NsisOptions-perMachine"></a>Mark "all users" (per-machine) as default. Not recommended. Defaults to `false`.
130130
| allowElevation | <a name="NsisOptions-allowElevation"></a>Allow requesting for elevation. If false, user will have to restart installer with elevated permissions. Defaults to `true`.
131131
| oneClick | <a name="NsisOptions-oneClick"></a>One-click installation. Defaults to `true`.
132+
| installerHeader | <a name="NsisOptions-installerHeader"></a>*boring installer only.* `MUI_HEADERIMAGE`, relative to the project directory. Defaults to `build/installerHeader.bmp`
132133

133134
<a name="LinuxBuildOptions"></a>
134135
### `.build.linux`

src/metadata.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ export interface NsisOptions {
368368
One-click installation. Defaults to `true`.
369369
*/
370370
readonly oneClick?: boolean | null
371+
372+
/*
373+
*boring installer only.* `MUI_HEADERIMAGE`, relative to the project directory. Defaults to `build/installerHeader.bmp`
374+
*/
375+
readonly installerHeader?: string | null
371376
}
372377

373378
/*

src/platformPackager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export interface PackagerOptions {
4444
* Application `package.json` will be still read, but options specified in this object will override.
4545
*/
4646
readonly appMetadata?: AppMetadata
47+
48+
readonly effectiveOptionComputed?: (options: any) => boolean
4749
}
4850

4951
export interface BuildInfo {
@@ -70,7 +72,7 @@ export class Target {
7072
}
7173

7274
export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions> {
73-
protected readonly options: PackagerOptions
75+
readonly options: PackagerOptions
7476

7577
readonly projectDir: string
7678
readonly buildResourcesDir: string

src/targets/nsis.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3"
2424
const nsisPathPromise = getBin("nsis", NSIS_VERSION, `https://dl.bintray.com/electron-userland/bin/${NSIS_VERSION}.7z`, NSIS_SHA2)
2525

2626
export default class NsisTarget extends Target {
27-
private readonly nsisOptions: NsisOptions
27+
private readonly options: NsisOptions
2828

2929
constructor(private packager: WinPackager) {
3030
super("nsis")
3131

32-
this.nsisOptions = packager.info.devMetadata.build.nsis || Object.create(null)
32+
this.options = packager.info.devMetadata.build.nsis || Object.create(null)
3333
}
3434

3535
async build(arch: Arch, outDir: string, appOutDir: string) {
@@ -43,7 +43,7 @@ export default class NsisTarget extends Target {
4343
// const archiveFile = path.join(this.outDir, `.${packager.metadata.name}-${packager.metadata.version}${archSuffix}.7z`)
4444
const archiveFile = path.join(outDir, `app.7z`)
4545

46-
const guid = this.nsisOptions.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id})
46+
const guid = this.options.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id})
4747
const productName = appInfo.productName
4848
const defines: any = {
4949
APP_ID: appInfo.id,
@@ -60,14 +60,31 @@ export default class NsisTarget extends Target {
6060
COMPANY_NAME: appInfo.companyName,
6161
}
6262

63-
if (this.nsisOptions.perMachine === true) {
63+
let installerHeader = this.options.installerHeader
64+
if (installerHeader === undefined) {
65+
const resourceList = await packager.resourceList
66+
if (resourceList.includes("installerHeader.bmp")) {
67+
installerHeader = path.join(packager.buildResourcesDir, "installerHeader.bmp")
68+
}
69+
}
70+
else {
71+
installerHeader = path.resolve(packager.projectDir, installerHeader)
72+
}
73+
74+
if (installerHeader != null) {
75+
defines.MUI_HEADERIMAGE = null
76+
defines.MUI_HEADERIMAGE_RIGHT = null
77+
defines.MUI_HEADERIMAGE_BITMAP = installerHeader
78+
}
79+
80+
if (this.options.perMachine === true) {
6481
defines.MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS = null
6582
}
6683
else {
6784
defines.MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER = null
6885
}
6986

70-
if (this.nsisOptions.allowElevation !== false) {
87+
if (this.options.allowElevation !== false) {
7188
defines.MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = null
7289
}
7390

@@ -102,14 +119,18 @@ export default class NsisTarget extends Target {
102119

103120
await subTask("Packing app into 7z archive", archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true))
104121

105-
const oneClick = this.nsisOptions.oneClick !== false
122+
const oneClick = this.options.oneClick !== false
106123
if (oneClick) {
107124
defines.ONE_CLICK = null
108125
}
109126

110127
debug(defines)
111128
debug(commands)
112129

130+
if (this.packager.options.effectiveOptionComputed != null && this.packager.options.effectiveOptionComputed([defines, commands])) {
131+
return
132+
}
133+
113134
await subTask(`Executing makensis`, NsisTarget.executeMakensis(defines, commands))
114135
await packager.sign(installerPath)
115136

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:bd146e39af28cec251d63da453cb8f04b74904a643f4c3dab52ef2b5bcb4bb3a
3+
size 9744

test/src/helpers/packTester.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions
100100

101101
const platformToTarget = await packager.build()
102102

103-
if (packagerOptions.platformPackagerFactory != null) {
103+
if (packagerOptions.platformPackagerFactory != null || packagerOptions.effectiveOptionComputed != null) {
104104
return
105105
}
106106

test/src/winPackagerTest.ts

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Platform, Arch, BuildInfo, PackagerOptions } from "out"
22
import test from "./helpers/avaEx"
33
import { assertPack, platform, modifyPackageJson, signed } from "./helpers/packTester"
4-
import { move, outputFile } from "fs-extra-p"
4+
import { outputFile, rename } from "fs-extra-p"
55
import * as path from "path"
66
import { WinPackager } from "out/winPackager"
77
import { Promise as BluebirdPromise } from "bluebird"
@@ -48,10 +48,76 @@ test.ifNotCiOsx("nsis boring", () => assertPack("test-app-one", _signed({
4848
}
4949
))
5050

51-
// test.ifNotCiOsx("win 32", () => assertPack("test-app-one", signed({
52-
// targets: Platform.WINDOWS.createTarget(null, Arch.ia32),
53-
// })
54-
// ))
51+
test.ifNotCiOsx("nsis boring", () => assertPack("test-app-one", _signed({
52+
targets: Platform.WINDOWS.createTarget(["nsis"]),
53+
devMetadata: {
54+
build: {
55+
nsis: {
56+
oneClick: false,
57+
}
58+
}
59+
}
60+
}), {
61+
useTempDir: true,
62+
}
63+
))
64+
65+
test.ifNotCiOsx("nsis boring, MUI_HEADER", () => {
66+
let installerHeaderPath: string | null = null
67+
return assertPack("test-app-one", {
68+
targets: Platform.WINDOWS.createTarget(["nsis"]),
69+
devMetadata: {
70+
build: {
71+
nsis: {
72+
oneClick: false,
73+
}
74+
}
75+
},
76+
effectiveOptionComputed: options => {
77+
const defines = options[0]
78+
assertThat(defines.MUI_HEADERIMAGE).isEqualTo(null)
79+
assertThat(defines.MUI_HEADERIMAGE_BITMAP).isEqualTo(installerHeaderPath)
80+
assertThat(defines.MUI_HEADERIMAGE_RIGHT).isEqualTo(null)
81+
// speedup, do not build - another MUI_HEADER test will test build
82+
return true
83+
}
84+
}, {
85+
tempDirCreated: projectDir => {
86+
installerHeaderPath = path.join(projectDir, "build", "installerHeader.bmp")
87+
return rename(path.join(projectDir, "installerHeader.bmp"), installerHeaderPath)
88+
}
89+
}
90+
)
91+
})
92+
93+
test.ifNotCiOsx("nsis boring, MUI_HEADER as option", () => {
94+
let installerHeaderPath: string | null = null
95+
return assertPack("test-app-one", {
96+
targets: Platform.WINDOWS.createTarget(["nsis"]),
97+
devMetadata: {
98+
build: {
99+
nsis: {
100+
oneClick: false,
101+
installerHeader: "foo.bmp"
102+
}
103+
}
104+
},
105+
effectiveOptionComputed: options => {
106+
const defines = options[0]
107+
assertThat(defines.MUI_HEADERIMAGE).isEqualTo(null)
108+
assertThat(defines.MUI_HEADERIMAGE_BITMAP).isEqualTo(installerHeaderPath)
109+
assertThat(defines.MUI_HEADERIMAGE_RIGHT).isEqualTo(null)
110+
// test that we can build such installer
111+
return false
112+
}
113+
}, {
114+
tempDirCreated: projectDir => {
115+
installerHeaderPath = path.join(projectDir, "foo.bmp")
116+
return rename(path.join(projectDir, "installerHeader.bmp"), installerHeaderPath)
117+
}
118+
}
119+
)
120+
})
55121

56122
// very slow
57123
test.ifWinCi("delta and msi", () => assertPack("test-app-one", {
@@ -95,17 +161,17 @@ test("detect install-spinner, certificateFile/password", () => {
95161
targets: Platform.WINDOWS.createTarget(),
96162
platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingWinPackager(packager, cleanupTasks),
97163
devMetadata: {
98-
build: {
99-
win: {
100-
certificatePassword: "pass",
101-
}
164+
build: {
165+
win: {
166+
certificatePassword: "pass",
102167
}
103168
}
169+
}
104170
}, {
105171
tempDirCreated: it => {
106172
loadingGifPath = path.join(it, "build", "install-spinner.gif")
107173
return BluebirdPromise.all([
108-
move(path.join(it, "install-spinner.gif"), loadingGifPath),
174+
rename(path.join(it, "install-spinner.gif"), loadingGifPath),
109175
modifyPackageJson(it, data => {
110176
data.build.win = {
111177
certificateFile: "secretFile",
@@ -123,7 +189,7 @@ test("detect install-spinner, certificateFile/password", () => {
123189
})
124190

125191
test.ifNotCiOsx("icon < 256", (t: any) => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), {
126-
tempDirCreated: projectDir => move(path.join(projectDir, "build", "incorrect.ico"), path.join(projectDir, "build", "icon.ico"), {clobber: true})
192+
tempDirCreated: projectDir => rename(path.join(projectDir, "build", "incorrect.ico"), path.join(projectDir, "build", "icon.ico"))
127193
}), /Windows icon size must be at least 256x256, please fix ".+/))
128194

129195
test.ifNotCiOsx("icon not an image", (t: any) => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), {
@@ -137,7 +203,7 @@ test.ifOsx("custom icon", () => {
137203
platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingWinPackager(packager, cleanupTasks)
138204
}, {
139205
tempDirCreated: projectDir => BluebirdPromise.all([
140-
move(path.join(projectDir, "build", "icon.ico"), path.join(projectDir, "customIcon.ico")),
206+
rename(path.join(projectDir, "build", "icon.ico"), path.join(projectDir, "customIcon.ico")),
141207
modifyPackageJson(projectDir, data => {
142208
data.build.win = {
143209
icon: "customIcon"

0 commit comments

Comments
 (0)