Many packager improvements; Windows signing! (#413)

* Many packager improvements; Windows signing!

* Windows signing works now! (Certs are on an external USB stick that
must be plugged into the build machine during build. We can't do the
same for OS X because certs need to exist in the login Keychain to be
found.)

Fixes #219

* Signing is now optional (so OS X and Windows contributors can run
`npm run package` without errors)

* zip, dmg, and deb arguments are now passed in as e.g. "--package=dmg"

* Print a huge warning when signing is disabled so we're less likely to
ship unsigned binaries to users.

* Make console.logs during packaging consistent and parallel
("creating..." followed by "created.")

* More aggressive signing warnings

* Warn when building OS X app on non-OS X platform (because signing
will never work on non-OS X platforms)
* Warn when building Windows app on non-Windows platform (because
signing doesn't work yet on non-Windows platforms)
This commit is contained in:
Feross Aboukhadijeh
2016-04-14 22:32:36 -07:00
parent 04318d7580
commit 2333171de7
5 changed files with 165 additions and 58 deletions

View File

@@ -50,12 +50,15 @@ $ npm run package
To build for one platform:
```
$ npm run package -- [platform] [package-type]
$ npm run package -- [platform]
```
Where `[platform]` is `darwin`, `linux`, or `win32`
Where `[platform]` is `darwin`, `linux`, `win32`, or `all` (default).
and `[package-type]` is `all` (default), `deb` or `zip` (`linux` platform only)
The following optional arguments are available:
- `--package=[type]` - Package only one output file. `type` is `deb`, `dmg`, `zip`, or `all` (default)
- `--sign` - Sign the application (OS X, Windows)
#### Windows build notes

View File

@@ -4,32 +4,55 @@
* Builds app binaries for OS X, Linux, and Windows.
*/
var config = require('../config')
var cp = require('child_process')
var electronPackager = require('electron-packager')
var fs = require('fs')
var minimist = require('minimist')
var path = require('path')
var pkg = require('../package.json')
var rimraf = require('rimraf')
var config = require('../config')
var pkg = require('../package.json')
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
/*
* Path to folder with the following files:
* - Windows Authenticode private key and cert (authenticode.p12)
* - Windows Authenticode password file (authenticode.txt)
*/
var CERT_PATH = process.platform === 'win32'
? 'D:'
: '/Volumes/Certs'
var argv = minimist(process.argv.slice(2), {
boolean: [
'sign'
],
default: {
package: 'all',
sign: false
},
string: [
'package'
]
})
function build () {
rimraf.sync(path.join(config.ROOT_PATH, 'dist'))
var platform = process.argv[2]
var packageType = process.argv.length > 3 ? process.argv[3] : 'all'
var platform = argv._[0]
if (platform === 'darwin') {
buildDarwin(printDone)
} else if (platform === 'win32') {
buildWin32(printDone)
} else if (platform === 'linux') {
buildLinux(packageType, printDone)
buildLinux(printDone)
} else {
buildDarwin(function (err, buildPath) {
printDone(err, buildPath)
buildWin32(function (err, buildPath) {
printDone(err, buildPath)
buildLinux(packageType, printDone)
buildLinux(printDone)
})
})
}
@@ -140,13 +163,25 @@ var linux = {
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
}
/*
* Print a large warning when signing is disabled so we are less likely to accidentally
* ship unsigned binaries to users.
*/
process.on('exit', function () {
if (!argv.sign) {
printWarning()
}
})
build()
function buildDarwin (cb) {
var plist = require('plist')
console.log('OS X: Packaging electron...')
electronPackager(Object.assign({}, all, darwin), function (err, buildPath) {
if (err) return cb(err)
console.log('OS X: Packaged electron.')
var appPath = path.join(buildPath[0], config.APP_NAME + '.app')
var contentsPath = path.join(appPath, 'Contents')
@@ -189,11 +224,24 @@ function buildDarwin (cb) {
cp.execSync(`cp ${config.APP_FILE_ICON + '.icns'} ${resourcesPath}`)
if (process.platform === 'darwin') {
var appDmg = require('appdmg')
if (argv.sign) {
signApp(function (err) {
if (err) return cb(err)
pack()
})
} else {
printWarning()
pack()
}
} else {
printWarning()
}
function signApp (cb) {
var sign = require('electron-osx-sign')
/*
* Sign the app with Apple Developer ID certificate. We sign the app for 2 reasons:
* Sign the app with Apple Developer ID certificates. We sign the app for 2 reasons:
* - So the auto-updater (Squirrrel.Mac) can check that app updates are signed by
* the same author as the current version.
* - So users will not a see a warning about the app coming from an "Unidentified
@@ -211,48 +259,68 @@ function buildDarwin (cb) {
verbose: true
}
console.log('OS X: Signing app...')
sign(signOpts, function (err) {
if (err) return cb(err)
console.log('OS X: Signed app.')
cb(null)
})
}
// Create .zip file (used by the auto-updater)
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-darwin.zip')
cp.execSync(`cd ${buildPath[0]} && zip -r -y ${zipPath} ${config.APP_NAME + '.app'}`)
console.log('Created OS X .zip file.')
function pack () {
if (argv.package === 'zip' || argv.package === 'all') {
packageZip()
}
if (argv.package === 'dmg' || argv.package === 'all') {
packageDmg()
}
}
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
rimraf.sync(targetPath)
function packageZip () {
// Create .zip file (used by the auto-updater)
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-darwin.zip')
console.log('OS X: Creating zip...')
cp.execSync(`cd ${buildPath[0]} && zip -r -y ${zipPath} ${config.APP_NAME + '.app'}`)
console.log('OS X: Created zip.')
}
// Create a .dmg (OS X disk image) file, for easy user installation.
var dmgOpts = {
basepath: config.ROOT_PATH,
target: targetPath,
specification: {
title: config.APP_NAME,
icon: config.APP_ICON + '.icns',
background: path.join(config.STATIC_PATH, 'appdmg.png'),
'icon-size': 128,
contents: [
{ x: 122, y: 240, type: 'file', path: appPath },
{ x: 380, y: 240, type: 'link', path: '/Applications' },
// Hide hidden icons out of view, for users who have hidden files shown.
// https://github.com/LinusU/node-appdmg/issues/45#issuecomment-153924954
{ x: 50, y: 500, type: 'position', path: '.background' },
{ x: 100, y: 500, type: 'position', path: '.DS_Store' },
{ x: 150, y: 500, type: 'position', path: '.Trashes' },
{ x: 200, y: 500, type: 'position', path: '.VolumeIcon.icns' }
]
}
function packageDmg () {
var appDmg = require('appdmg')
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
rimraf.sync(targetPath)
// Create a .dmg (OS X disk image) file, for easy user installation.
var dmgOpts = {
basepath: config.ROOT_PATH,
target: targetPath,
specification: {
title: config.APP_NAME,
icon: config.APP_ICON + '.icns',
background: path.join(config.STATIC_PATH, 'appdmg.png'),
'icon-size': 128,
contents: [
{ x: 122, y: 240, type: 'file', path: appPath },
{ x: 380, y: 240, type: 'link', path: '/Applications' },
// Hide hidden icons out of view, for users who have hidden files shown.
// https://github.com/LinusU/node-appdmg/issues/45#issuecomment-153924954
{ x: 50, y: 500, type: 'position', path: '.background' },
{ x: 100, y: 500, type: 'position', path: '.DS_Store' },
{ x: 150, y: 500, type: 'position', path: '.Trashes' },
{ x: 200, y: 500, type: 'position', path: '.VolumeIcon.icns' }
]
}
}
var dmg = appDmg(dmgOpts)
dmg.on('error', cb)
dmg.on('progress', function (info) {
if (info.type === 'step-begin') console.log(info.title + '...')
})
dmg.on('finish', function (info) {
console.log('Created OS X disk image (.dmg) file.')
cb(null, buildPath)
})
console.log('OS X: Creating dmg...')
var dmg = appDmg(dmgOpts)
dmg.on('error', cb)
dmg.on('progress', function (info) {
if (info.type === 'step-begin') console.log(info.title + '...')
})
dmg.on('finish', function (info) {
console.log('OS X: Created dmg.')
cb(null, buildPath)
})
}
})
@@ -261,60 +329,79 @@ function buildDarwin (cb) {
function buildWin32 (cb) {
var installer = require('electron-winstaller')
console.log('Windows: Packaging electron...')
electronPackager(Object.assign({}, all, win32), function (err, buildPath) {
if (err) return cb(err)
console.log('Windows: Packaged electron.')
console.log('Creating Windows installer...')
var signWithParams
if (process.platform === 'win32') {
if (argv.sign) {
var certificateFile = path.join(CERT_PATH, 'authenticode.p12')
var certificatePassword = fs.readFileSync(path.join(CERT_PATH, 'authenticode.txt'), 'utf8')
var timestampServer = 'http://timestamp.comodoca.com'
signWithParams = `/a /f "${certificateFile}" /p "${certificatePassword}" /tr "${timestampServer}" /td sha256`
} else {
printWarning()
}
} else {
printWarning()
}
console.log('Windows: Creating installer...')
installer.createWindowsInstaller({
appDirectory: buildPath[0],
authors: config.APP_TEAM,
// certificateFile: '', // TODO
description: config.APP_NAME,
exe: config.APP_NAME + '.exe',
iconUrl: config.GITHUB_URL_RAW + '/static/' + config.APP_NAME + '.ico',
loadingGif: path.join(config.STATIC_PATH, 'loading.gif'),
remoteReleases: config.GITHUB_URL,
name: config.APP_NAME,
noMsi: true,
outputDirectory: path.join(config.ROOT_PATH, 'dist'),
productName: config.APP_NAME,
remoteReleases: config.GITHUB_URL,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
setupIcon: config.APP_ICON + '.ico',
signWithParams: signWithParams,
title: config.APP_NAME,
usePackageJson: false,
version: pkg.version
}).then(function () {
console.log('Created Windows installer.')
console.log('Windows: Created installer.')
cb(null, buildPath)
}).catch(cb)
})
}
function buildLinux (packageType, cb) {
function buildLinux (cb) {
var distPath = path.join(config.ROOT_PATH, 'dist')
console.log('Linux: Packaging electron...')
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
if (err) return cb(err)
console.log('Linux: Packaged electron.')
for (var i = 0; i < buildPath.length; i++) {
var filesPath = buildPath[i]
var destArch = filesPath.split('-').pop()
if (packageType === 'deb' || packageType === 'all') {
if (argv.package === 'deb' || argv.package === 'all') {
packageDeb(filesPath, destArch)
}
if (packageType === 'zip' || packageType === 'all') {
if (argv.package === 'zip' || argv.package === 'all') {
packageZip(filesPath, destArch)
}
}
})
function packageDeb (filesPath, destArch) {
// Create .deb file for debian based platforms
// Create .deb file for Debian-based platforms
var deb = require('nobin-debian-installer')()
var destPath = path.join('/opt', pkg.name)
console.log(`Linux: Creating ${destArch} deb...`)
deb.pack({
package: pkg,
info: {
@@ -333,7 +420,7 @@ function buildLinux (packageType, cb) {
cwd: filesPath
}], function (err) {
if (err) return console.error(err.message || err)
console.log('Created Linux ' + destArch + ' .deb file.')
console.log(`Linux: Created ${destArch} deb.`)
})
}
@@ -341,8 +428,9 @@ function buildLinux (packageType, cb) {
// Create .zip file for Linux
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-linux-' + destArch + '.zip')
var appFolderName = path.basename(filesPath)
console.log(`Linux: Creating ${destArch} zip...`)
cp.execSync(`cd ${distPath} && zip -r -y ${zipPath} ${appFolderName}`)
console.log('Created Linux ' + destArch + ' .zip file.')
console.log(`Linux: Created ${destArch} zip.`)
}
}
@@ -350,3 +438,7 @@ function printDone (err, buildPath) {
if (err) console.error(err.message || err)
else console.log('Built ' + buildPath[0])
}
function printWarning () {
console.log(fs.readFileSync(path.join(__dirname, 'warning.txt'), 'utf8'))
}

View File

@@ -2,7 +2,7 @@
set -e
git diff --exit-code
npm run package
npm run package -- --sign
git push
git push --tags
npm publish

12
bin/warning.txt Normal file
View File

@@ -0,0 +1,12 @@
*********************************************************
_ _ ___ ______ _ _ _____ _ _ _____
| | | |/ _ \ | ___ \ \ | |_ _| \ | | __ \
| | | / /_\ \| |_/ / \| | | | | \| | | \/
| |/\| | _ || /| . ` | | | | . ` | | __
\ /\ / | | || |\ \| |\ |_| |_| |\ | |_\ \
\/ \/\_| |_/\_| \_\_| \_/\___/\_| \_/\____/
Application is NOT signed. Do not ship this to users!
*********************************************************

View File

@@ -47,6 +47,7 @@
"electron-packager": "^6.0.2",
"electron-winstaller": "feross/windows-installer#build",
"gh-release": "^2.0.3",
"minimist": "^1.2.0",
"nobin-debian-installer": "^0.0.9",
"plist": "^1.2.0",
"standard": "^6.0.5"
@@ -71,7 +72,6 @@
"scripts": {
"clean": "node ./bin/clean.js",
"package": "npm install && npm prune && npm dedupe && node ./bin/package.js",
"size": "npm run package -- darwin && du -ch dist/WebTorrent-darwin-x64 | grep total",
"start": "electron .",
"test": "standard",
"update-authors": "./bin/update-authors.sh"