Skip to content

Commit d8762db

Browse files
authored
feat: NSIS target (#495)
Closes #472 #493
1 parent 3fc7a89 commit d8762db

29 files changed

+1256
-68
lines changed

.idea/dictionaries/develar.xml

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

docker/nsis.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
rm -rf Docs
5+
rm -rf NSIS.chm
6+
rm -rf Examples
7+
rm -rf Plugins/x86-ansi
8+
9+
# nsProcess plugin
10+
curl -L http://nsis.sourceforge.net/mediawiki/images/1/18/NsProcess.zip > a.zip
11+
7za x a.zip -oa
12+
mv a/Plugin/nsProcessW.dll Plugins/x86-unicode/nsProcess.dll
13+
mv a/Include/nsProcess.nsh Include/nsProcess.nsh
14+
unlink a.zip
15+
rm -rf a
16+
17+
# UAC plugin
18+
curl -L http://nsis.sourceforge.net/mediawiki/images/8/8f/UAC.zip > a.zip
19+
7za x a.zip -oa
20+
mv a/Plugins/x86-unicode/UAC.dll Plugins/x86-unicode/UAC.dll
21+
mv a/UAC.nsh Include/UAC.nsh
22+
unlink a.zip
23+
rm -rf a
24+
25+
# WinShell
26+
curl -L http://nsis.sourceforge.net/mediawiki/images/5/54/WinShell.zip > a.zip
27+
7za x a.zip -oa
28+
mv a/Plugins/x86-unicode/WinShell.dll Plugins/x86-unicode/WinShell.dll
29+
unlink a.zip
30+
rm -rf a
31+
32+
dir=${PWD##*/}
33+
cd ..
34+
rm -rf ${dir}.7z
35+
7za a -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ${dir}.7z ${dir}

docs/NSIS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# GUID vs Application Name
2+
3+
Windows requires to use registry keys (e.g. INSTALL/UNINSTALL info). Squirrel.Windows simply uses application name as key.
4+
But it is not robust — Google can use key Google Chrome SxS, because it is a Google.
5+
6+
So, it is better to use [GUID](http://stackoverflow.com/a/246935/1910191).
7+
You are not forced to explicitly specify it — name-based [UUID v5](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_5_.28SHA-1_hash_.26_namespace.29) will be generated from your [appId](https://github.com/electron-userland/electron-builder/wiki/Options#BuildMetadata-appId) or [name](https://github.com/electron-userland/electron-builder/wiki/Options#AppMetadata-name).
8+
It means that you **should not change appId** once your application in use (or name if `appId` was not set). Application product name (title) or description can be safely changed.
9+
10+
You can explicitly set guid using option [nsis.guid](https://github.com/electron-userland/electron-builder/wiki/Options#NsisOptions-guid), but it is not recommended — consider using [appId](https://github.com/electron-userland/electron-builder/wiki/Options#BuildMetadata-appId).

docs/Options.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Here documented only `electron-builder` specific options:
5050
## `.build`
5151
| Name | Description
5252
| --- | ---
53-
| app-bundle-id | <a name="BuildMetadata-app-bundle-id"></a>*OS X-only.* The app bundle ID. See [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070).
53+
| appId | <a name="BuildMetadata-appId"></a><p>The application id. Used as [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070) for OS X and as [Application User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx) for Windows.</p> <p>For windows only NSIS target supports it. Squirrel.Windows is not fixed yet.</p> <p>Defaults to <code>com.electron.${name}</code>. It is strongly recommended that an explicit ID be set.</p>
5454
| app-category-type | <a name="BuildMetadata-app-category-type"></a><p>*OS X-only.* The application category type, as shown in the Finder via *View -&gt; Arrange by Application Category* when viewing the Applications directory.</p> <p>For example, <code>app-category-type=public.app-category.developer-tools</code> will set the application category to *Developer Tools*.</p> <p>Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).</p>
5555
| asar | <a name="BuildMetadata-asar"></a><p>Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). Defaults to <code>true</code>. Reasons why you may want to disable this feature are described in [an application packaging tutorial in Electron’s documentation](http://electron.atom.io/docs/latest/tutorial/application-packaging/#limitations-on-node-api/).</p> <p>Or you can pass object of any asar options.</p>
5656
| productName | <a name="BuildMetadata-productName"></a>See [AppMetadata.productName](#AppMetadata-productName).
@@ -60,6 +60,7 @@ Here documented only `electron-builder` specific options:
6060
| osx | <a name="BuildMetadata-osx"></a>See [.build.osx](#OsXBuildOptions).
6161
| mas | <a name="BuildMetadata-mas"></a>See [.build.mas](#MasBuildOptions).
6262
| win | <a name="BuildMetadata-win"></a>See [.build.win](#LinuxBuildOptions).
63+
| nsis | <a name="BuildMetadata-nsis"></a>See [.build.nsis](#NsisOptions).
6364
| linux | <a name="BuildMetadata-linux"></a>See [.build.linux](#LinuxBuildOptions).
6465
| compression | <a name="BuildMetadata-compression"></a>The compression level, one of `store`, `normal`, `maximum` (default: `normal`). If you want to rapidly test build, `store` can reduce build time significantly.
6566
| afterPack | <a name="BuildMetadata-afterPack"></a>*programmatic API only* The function to be run after pack (but before pack into distributable format and sign). Promise must be returned.
@@ -100,6 +101,17 @@ MAS (Mac Application Store) specific options (in addition to `build.osx`).
100101
| remoteToken | <a name="WinBuildOptions-remoteToken"></a>Authentication token for remote updates
101102
| signingHashAlgorithms | <a name="WinBuildOptions-signingHashAlgorithms"></a>Array of signing algorithms used. Defaults to `['sha1', 'sha256']`
102103

104+
<a name="NsisOptions"></a>
105+
### `.build.nsis`
106+
107+
NSIS target support in progress — not polished and not fully tested and checked.
108+
109+
| Name | Description
110+
| --- | ---
111+
| perMachine | <a name="NsisOptions-perMachine"></a>Mark "all users" (per-machine) as default. Not recommended. Defaults to `false`.
112+
| allowElevation | <a name="NsisOptions-allowElevation"></a>Allow requesting for elevation. If false, user will have to restart installer with elevated permissions. Defaults to `true`.
113+
| oneClick | <a name="NsisOptions-oneClick"></a>One-click installation. Defaults to `true`.
114+
103115
<a name="LinuxBuildOptions"></a>
104116
### `.build.linux`
105117
| Name | Description

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"read-package-json": "^2.0.4",
8080
"signcode-tf": "~0.7.3",
8181
"source-map-support": "^0.4.0",
82+
"uuid-1345": "^0.99.6",
8283
"yargs": "^4.7.1"
8384
},
8485
"optionalDependencies": {
@@ -128,5 +129,8 @@
128129
"test/out/*"
129130
]
130131
},
131-
"typings": "./out/electron-builder.d.ts"
132+
"typings": "./out/electron-builder.d.ts",
133+
"publishConfig": {
134+
"tag": "next"
135+
}
132136
}

src/errorMessages.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
export const buildIsMissed = `Please specify 'build' configuration in the development package.json ('%s'), at least
22
33
build: {
4-
"app-bundle-id": "your.id",
5-
"app-category-type": "your.app.category.type",
6-
"iconUrl": "see https://github.com/develar/electron-builder#in-short",
4+
"appId": "your.id",
5+
"app-category-type": "your.app.category.type"
76
}
87
}
98

src/fpmDownload.ts

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,64 @@ const versionToPromise = new Map<string, BluebirdPromise<string>>()
1313

1414
// can be called in parallel, all calls for the same version will get the same promise - will be downloaded only once
1515
export function downloadFpm(version: string, osAndArch: string): Promise<string> {
16-
let promise = versionToPromise.get(version)
16+
return getBin("fpm", `fpm-${version}-${osAndArch}`, `https://github.com/develar/fpm-self-contained/releases/download/v${version}/${`fpm-${version}-${osAndArch}`}.7z`)
17+
.then(it => path.join(it, "fpm"))
18+
}
19+
20+
export function getBin(name: string, dirName: string, url: string, sha1?: string): Promise<string> {
21+
let promise = versionToPromise.get(dirName)
1722
// if rejected, we will try to download again
18-
if (<any>promise != null && !promise!.isRejected()) {
19-
return promise!
23+
if (promise != null && !promise.isRejected()) {
24+
return promise
2025
}
2126

22-
promise = <BluebirdPromise<string>>doDownloadFpm(version, osAndArch)
23-
versionToPromise.set(version, promise)
27+
promise = <BluebirdPromise<string>>doGetBin(name, dirName, url, sha1)
28+
versionToPromise.set(dirName, promise)
2429
return promise
2530
}
2631

27-
async function doDownloadFpm(version: string, osAndArch: string): Promise<string> {
28-
const dirName = `fpm-${version}-${osAndArch}`
29-
const url = `https://github.com/develar/fpm-self-contained/releases/download/v${version}/${dirName}.7z`
32+
// we cache in the global location - in the home dir, not in the node_modules/.cache (https://www.npmjs.com/package/find-cache-dir) because
33+
// * don't need to find node_modules
34+
// * don't pollute user project dir (important in case of 1-package.json project structure)
35+
// * simplify/speed-up tests (don't download fpm for each test project)
36+
async function doGetBin(name: string, dirName: string, url: string, sha2?: string): Promise<string> {
37+
const cachePath = path.join(homedir(), ".cache", name)
38+
const dirPath = path.join(cachePath, dirName)
3039

31-
// we cache in the global location - in the home dir, not in the node_modules/.cache (https://www.npmjs.com/package/find-cache-dir) because
32-
// * don't need to find node_modules
33-
// * don't pollute user project dir (important in case of 1-package.json project structure)
34-
// * simplify/speed-up tests (don't download fpm for each test project)
35-
const cacheDir = path.join(homedir(), ".cache", "fpm")
36-
const fpmDir = path.join(cacheDir, dirName)
37-
38-
const fpmDirStat = await statOrNull(fpmDir)
39-
if (fpmDirStat != null && fpmDirStat.isDirectory()) {
40-
debug(`Found existing fpm ${fpmDir}`)
41-
return path.join(fpmDir, "fpm")
40+
const dirStat = await statOrNull(dirPath)
41+
if (dirStat != null && dirStat.isDirectory()) {
42+
debug(`Found existing ${name} ${dirPath}`)
43+
return dirPath
4244
}
4345

4446
// 7z cannot be extracted from the input stream, temp file is required
45-
const tempUnpackDir = path.join(cacheDir, getTempName())
47+
const tempUnpackDir = path.join(cachePath, getTempName())
4648
const archiveName = `${tempUnpackDir}.7z`
47-
debug(`Download fpm from ${url} to ${archiveName}`)
48-
// 7z doesn't create out dir
49+
debug(`Download ${name} from ${url} to ${archiveName}`)
50+
// 7z doesn't create out dir, so, we don't create dir in parallel to download - dir creation will create parent dirs for archive file also
4951
await emptyDir(tempUnpackDir)
50-
await download(url, archiveName, false)
52+
await download(url, archiveName, {
53+
skipDirCreation: true,
54+
sha2: sha2,
55+
})
5156

5257
await spawn(path7za, debug7zArgs("x").concat(archiveName, `-o${tempUnpackDir}`), {
53-
cwd: cacheDir,
58+
cwd: cachePath,
5459
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
5560
})
5661

5762
await BluebirdPromise.all([
58-
rename(path.join(tempUnpackDir, dirName), fpmDir)
63+
rename(path.join(tempUnpackDir, dirName), dirPath)
5964
.catch(e => {
60-
console.warn("Cannot move downloaded fpm into final location (another process downloaded faster?): " + e)
65+
console.warn(`Cannot move downloaded ${name} into final location (another process downloaded faster?): ${e}`)
6166
}),
6267
unlink(archiveName),
6368
])
6469
await BluebirdPromise.all([
6570
remove(tempUnpackDir),
66-
writeFile(path.join(fpmDir, ".lastUsed"), Date.now().toString())
71+
writeFile(path.join(dirPath, ".lastUsed"), Date.now().toString())
6772
])
6873

69-
debug(`fpm downloaded to ${fpmDir}`)
70-
return path.join(fpmDir, "fpm")
74+
debug(`${name}} downloaded to ${dirPath}`)
75+
return dirPath
7176
}

src/httpRequest.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ import * as path from "path"
88

99
const maxRedirects = 10
1010

11-
export const download = <(url: string, destination: string, isCreateDir?: boolean | undefined) => BluebirdPromise<any>>(BluebirdPromise.promisify(_download))
11+
export interface DownloadOptions {
12+
skipDirCreation?: boolean
13+
sha2?: string
14+
}
15+
16+
export const download = <(url: string, destination: string, options?: DownloadOptions) => BluebirdPromise<any>>(BluebirdPromise.promisify(_download))
1217

13-
function _download(url: string, destination: string, isCreateDir: boolean | undefined, callback: (error: Error) => void): void {
18+
function _download(url: string, destination: string, options: DownloadOptions | n, callback: (error: Error) => void): void {
1419
if (callback == null) {
15-
callback = <any>isCreateDir
16-
isCreateDir = true
20+
callback = <any>options
21+
options = null
1722
}
18-
doDownload(url, destination, 0, isCreateDir === undefined ? true : isCreateDir, callback)
23+
doDownload(url, destination, 0, options || {}, callback)
1924
}
2025

2126
export function addTimeOutHandler(request: ClientRequest, callback: (error: Error) => void) {
@@ -27,8 +32,8 @@ export function addTimeOutHandler(request: ClientRequest, callback: (error: Erro
2732
})
2833
}
2934

30-
function doDownload(url: string, destination: string, redirectCount: number, isCreateDir: boolean, callback: (error: Error) => void) {
31-
const ensureDirPromise = isCreateDir ? ensureDir(path.dirname(destination)) : BluebirdPromise.resolve()
35+
function doDownload(url: string, destination: string, redirectCount: number, options: DownloadOptions, callback: (error: Error) => void) {
36+
const ensureDirPromise = options.skipDirCreation ? BluebirdPromise.resolve() : ensureDir(path.dirname(destination))
3237

3338
const parsedUrl = parseUrl(url)
3439
// user-agent must be specified, otherwise some host can return 401 unauthorised
@@ -47,14 +52,25 @@ function doDownload(url: string, destination: string, redirectCount: number, isC
4752
const redirectUrl = response.headers.location
4853
if (redirectUrl != null) {
4954
if (redirectCount < maxRedirects) {
50-
doDownload(redirectUrl, destination, redirectCount++, isCreateDir, callback)
55+
doDownload(redirectUrl, destination, redirectCount++, options, callback)
5156
}
5257
else {
5358
callback(new Error("Too many redirects (> " + maxRedirects + ")"))
5459
}
5560
return
5661
}
5762

63+
const sha1Header = response.headers["X-Checksum-Sha1"]
64+
if (sha1Header != null && options.sha2 != null) {
65+
// todo why bintray doesn't send this header always
66+
if (sha1Header == null) {
67+
throw new Error("checksum is required, but server response doesn't contain X-Checksum-Sha2 header")
68+
}
69+
else if (sha1Header !== options.sha2) {
70+
throw new Error(`checksum mismatch: expected ${options.sha2} but got ${sha1Header} (X-Checksum-Sha2 header)`)
71+
}
72+
}
73+
5874
ensureDirPromise
5975
.then(() => {
6076
const downloadStream = createWriteStream(destination)

src/metadata.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,19 @@ export type CompressionLevel = "store" | "normal" | "maximum"
8181
*/
8282
export interface BuildMetadata {
8383
/*
84-
*OS X-only.* The app bundle ID. See [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070).
84+
The application id. Used as
85+
[CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070) for OS X and as
86+
[Application User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx) for Windows.
87+
88+
For windows only NSIS target supports it. Squirrel.Windows is not fixed yet.
89+
90+
Defaults to `com.electron.${name}`. It is strongly recommended that an explicit ID be set.
8591
*/
92+
readonly appId?: string | null
93+
94+
// deprecated
8695
readonly "app-bundle-id"?: string | null
96+
8797
/*
8898
*OS X-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory.
8999
@@ -150,6 +160,11 @@ export interface BuildMetadata {
150160
*/
151161
readonly win?: WinBuildOptions | null
152162

163+
/**
164+
See [.build.nsis](#NsisOptions).
165+
*/
166+
readonly nsis?: NsisOptions | null
167+
153168
/*
154169
See [.build.linux](#LinuxBuildOptions).
155170
*/
@@ -298,6 +313,30 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions {
298313
readonly signcodePath?: string | null
299314
}
300315

316+
/*
317+
### `.build.nsis`
318+
319+
NSIS target support in progress — not polished and not fully tested and checked.
320+
*/
321+
export interface NsisOptions {
322+
/*
323+
Mark "all users" (per-machine) as default. Not recommended. Defaults to `false`.
324+
*/
325+
readonly perMachine?: boolean | null
326+
327+
/*
328+
Allow requesting for elevation. If false, user will have to restart installer with elevated permissions. Defaults to `true`.
329+
*/
330+
readonly allowElevation?: boolean | null
331+
332+
readonly guid?: string | null
333+
334+
/*
335+
One-click installation. Defaults to `true`.
336+
*/
337+
readonly oneClick?: boolean | null
338+
}
339+
301340
/*
302341
### `.build.linux`
303342
*/

0 commit comments

Comments
 (0)