Windows Portable App (#417)
* packager: call callbacks consistently Before this, the callbacks would not being called, which would lead to an incomplete build on non-OS X platforms when trying to build all for all platforms. * packager: Always produce OS X update file regardless of --package option This makes it consistent with how the windows build always produces the .nupkg autoupdate files * packager: fix duplicate npm install Move "npm prune && npm dedupe" into the release script. Remove an extra "npm install" * Make Windows portable app When a folder named "Portable Settings" exists in same folder as WebTorrent.exe, then use it instead of the default application config path. Closes #358 * packager: remove redundant signing warning * cross platform zip function * Set config file path to match config.CONFIG_PATH * portable app: make electron settings portable * portable: fix poster/torrent paths * use cross-zip * portable app: default download folder inside 'Portable Settings'
This commit is contained in:
10
README.md
10
README.md
@@ -57,8 +57,16 @@ Where `[platform]` is `darwin`, `linux`, `win32`, or `all` (default).
|
||||
|
||||
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)
|
||||
- `--package=[type]` - Package single output type.
|
||||
- `deb` - Debian package
|
||||
- `zip` - Linux zip file
|
||||
- `dmg` - OS X disk image
|
||||
- `exe` - Windows installer
|
||||
- `portable` - Windows portable app
|
||||
- `all` - All platforms (default)
|
||||
|
||||
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows, *-darwin.zip for OS X) will always be produced.
|
||||
|
||||
#### Windows build notes
|
||||
|
||||
|
||||
176
bin/package.js
176
bin/package.js
@@ -8,8 +8,11 @@ var cp = require('child_process')
|
||||
var electronPackager = require('electron-packager')
|
||||
var fs = require('fs')
|
||||
var minimist = require('minimist')
|
||||
var mkdirp = require('mkdirp')
|
||||
var path = require('path')
|
||||
var rimraf = require('rimraf')
|
||||
var series = require('run-series')
|
||||
var zip = require('cross-zip')
|
||||
|
||||
var config = require('../config')
|
||||
var pkg = require('../package.json')
|
||||
@@ -25,6 +28,8 @@ var CERT_PATH = process.platform === 'win32'
|
||||
? 'D:'
|
||||
: '/Volumes/Certs'
|
||||
|
||||
var DIST_PATH = path.join(config.ROOT_PATH, 'dist')
|
||||
|
||||
var argv = minimist(process.argv.slice(2), {
|
||||
boolean: [
|
||||
'sign'
|
||||
@@ -39,7 +44,7 @@ var argv = minimist(process.argv.slice(2), {
|
||||
})
|
||||
|
||||
function build () {
|
||||
rimraf.sync(path.join(config.ROOT_PATH, 'dist'))
|
||||
rimraf.sync(DIST_PATH)
|
||||
var platform = argv._[0]
|
||||
if (platform === 'darwin') {
|
||||
buildDarwin(printDone)
|
||||
@@ -48,10 +53,10 @@ function build () {
|
||||
} else if (platform === 'linux') {
|
||||
buildLinux(printDone)
|
||||
} else {
|
||||
buildDarwin(function (err, buildPath) {
|
||||
printDone(err, buildPath)
|
||||
buildWin32(function (err, buildPath) {
|
||||
printDone(err, buildPath)
|
||||
buildDarwin(function (err) {
|
||||
printDone(err)
|
||||
buildWin32(function (err) {
|
||||
printDone(err)
|
||||
buildLinux(printDone)
|
||||
})
|
||||
})
|
||||
@@ -94,7 +99,7 @@ var all = {
|
||||
name: config.APP_NAME,
|
||||
|
||||
// The base directory where the finished package(s) are created.
|
||||
out: path.join(config.ROOT_PATH, 'dist'),
|
||||
out: DIST_PATH,
|
||||
|
||||
// Replace an already existing output directory.
|
||||
overwrite: true,
|
||||
@@ -163,16 +168,6 @@ 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) {
|
||||
@@ -181,7 +176,7 @@ function buildDarwin (cb) {
|
||||
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.')
|
||||
console.log('OS X: Packaged electron. ' + buildPath[0])
|
||||
|
||||
var appPath = path.join(buildPath[0], config.APP_NAME + '.app')
|
||||
var contentsPath = path.join(appPath, 'Contents')
|
||||
@@ -227,11 +222,11 @@ function buildDarwin (cb) {
|
||||
if (argv.sign) {
|
||||
signApp(function (err) {
|
||||
if (err) return cb(err)
|
||||
pack()
|
||||
pack(cb)
|
||||
})
|
||||
} else {
|
||||
printWarning()
|
||||
pack()
|
||||
pack(cb)
|
||||
}
|
||||
} else {
|
||||
printWarning()
|
||||
@@ -267,27 +262,31 @@ function buildDarwin (cb) {
|
||||
})
|
||||
}
|
||||
|
||||
function pack () {
|
||||
if (argv.package === 'zip' || argv.package === 'all') {
|
||||
packageZip()
|
||||
}
|
||||
function pack (cb) {
|
||||
packageZip() // always produce .zip file, used for automatic updates
|
||||
|
||||
if (argv.package === 'dmg' || argv.package === 'all') {
|
||||
packageDmg()
|
||||
packageDmg(cb)
|
||||
}
|
||||
}
|
||||
|
||||
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'}`)
|
||||
|
||||
var inPath = path.join(buildPath[0], config.APP_NAME + '.app')
|
||||
var outPath = path.join(DIST_PATH, BUILD_NAME + '-darwin.zip')
|
||||
zip(inPath, outPath)
|
||||
|
||||
console.log('OS X: Created zip.')
|
||||
}
|
||||
|
||||
function packageDmg () {
|
||||
function packageDmg (cb) {
|
||||
console.log('OS X: Creating dmg...')
|
||||
|
||||
var appDmg = require('appdmg')
|
||||
|
||||
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
|
||||
var targetPath = path.join(DIST_PATH, BUILD_NAME + '.dmg')
|
||||
rimraf.sync(targetPath)
|
||||
|
||||
// Create a .dmg (OS X disk image) file, for easy user installation.
|
||||
@@ -312,7 +311,6 @@ function buildDarwin (cb) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('OS X: Creating dmg...')
|
||||
var dmg = appDmg(dmgOpts)
|
||||
dmg.on('error', cb)
|
||||
dmg.on('progress', function (info) {
|
||||
@@ -320,7 +318,7 @@ function buildDarwin (cb) {
|
||||
})
|
||||
dmg.on('finish', function (info) {
|
||||
console.log('OS X: Created dmg.')
|
||||
cb(null, buildPath)
|
||||
cb(null)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -332,7 +330,7 @@ function buildWin32 (cb) {
|
||||
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('Windows: Packaged electron. ' + buildPath[0])
|
||||
|
||||
var signWithParams
|
||||
if (process.platform === 'win32') {
|
||||
@@ -348,65 +346,90 @@ function buildWin32 (cb) {
|
||||
printWarning()
|
||||
}
|
||||
|
||||
console.log('Windows: Creating installer...')
|
||||
installer.createWindowsInstaller({
|
||||
appDirectory: buildPath[0],
|
||||
authors: config.APP_TEAM,
|
||||
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'),
|
||||
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('Windows: Created installer.')
|
||||
cb(null, buildPath)
|
||||
}).catch(cb)
|
||||
var tasks = []
|
||||
if (argv.package === 'exe' || argv.package === 'all') {
|
||||
tasks.push((cb) => packageInstaller(cb))
|
||||
}
|
||||
if (argv.package === 'portable' || argv.package === 'all') {
|
||||
tasks.push((cb) => packagePortable(cb))
|
||||
}
|
||||
series(tasks, cb)
|
||||
|
||||
function packageInstaller (cb) {
|
||||
console.log('Windows: Creating installer...')
|
||||
installer.createWindowsInstaller({
|
||||
appDirectory: buildPath[0],
|
||||
authors: config.APP_TEAM,
|
||||
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'),
|
||||
name: config.APP_NAME,
|
||||
noMsi: true,
|
||||
outputDirectory: DIST_PATH,
|
||||
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('Windows: Created installer.')
|
||||
cb(null)
|
||||
}).catch(cb)
|
||||
}
|
||||
|
||||
function packagePortable (cb) {
|
||||
// Create Windows portable app
|
||||
console.log('Windows: Creating portable app...')
|
||||
|
||||
var portablePath = path.join(buildPath[0], 'Portable Settings')
|
||||
mkdirp.sync(portablePath)
|
||||
|
||||
var inPath = path.join(DIST_PATH, path.basename(buildPath[0]))
|
||||
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
|
||||
zip(inPath, outPath)
|
||||
|
||||
console.log('Windows: Created portable app.')
|
||||
cb(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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.')
|
||||
console.log('Linux: Packaged electron. ' + buildPath[0])
|
||||
|
||||
for (var i = 0; i < buildPath.length; i++) {
|
||||
var filesPath = buildPath[i]
|
||||
var tasks = []
|
||||
buildPath.forEach(function (filesPath) {
|
||||
var destArch = filesPath.split('-').pop()
|
||||
|
||||
if (argv.package === 'deb' || argv.package === 'all') {
|
||||
packageDeb(filesPath, destArch)
|
||||
tasks.push((cb) => packageDeb(filesPath, destArch, cb))
|
||||
}
|
||||
|
||||
if (argv.package === 'zip' || argv.package === 'all') {
|
||||
packageZip(filesPath, destArch)
|
||||
tasks.push((cb) => packageZip(filesPath, destArch, cb))
|
||||
}
|
||||
}
|
||||
})
|
||||
series(tasks, cb)
|
||||
})
|
||||
|
||||
function packageDeb (filesPath, destArch) {
|
||||
function packageDeb (filesPath, destArch, cb) {
|
||||
// Create .deb file for Debian-based platforms
|
||||
console.log(`Linux: Creating ${destArch} deb...`)
|
||||
|
||||
var deb = require('nobin-debian-installer')()
|
||||
var destPath = path.join('/opt', pkg.name)
|
||||
|
||||
console.log(`Linux: Creating ${destArch} deb...`)
|
||||
deb.pack({
|
||||
package: pkg,
|
||||
info: {
|
||||
arch: destArch === 'x64' ? 'amd64' : 'i386',
|
||||
targetDir: distPath,
|
||||
targetDir: DIST_PATH,
|
||||
depends: 'libc6 (>= 2.4)',
|
||||
scripts: {
|
||||
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'),
|
||||
@@ -419,26 +442,33 @@ function buildLinux (cb) {
|
||||
expand: true,
|
||||
cwd: filesPath
|
||||
}], function (err) {
|
||||
if (err) return console.error(err.message || err)
|
||||
if (err) return cb(err)
|
||||
console.log(`Linux: Created ${destArch} deb.`)
|
||||
cb(null)
|
||||
})
|
||||
}
|
||||
|
||||
function packageZip (filesPath, destArch) {
|
||||
function packageZip (filesPath, destArch, 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}`)
|
||||
|
||||
var inPath = path.join(DIST_PATH, path.basename(filesPath))
|
||||
var outPath = path.join(DIST_PATH, BUILD_NAME + '-linux-' + destArch + '.zip')
|
||||
zip(inPath, outPath)
|
||||
|
||||
console.log(`Linux: Created ${destArch} zip.`)
|
||||
cb(null)
|
||||
}
|
||||
}
|
||||
|
||||
function printDone (err, buildPath) {
|
||||
function printDone (err) {
|
||||
if (err) console.error(err.message || err)
|
||||
else console.log('Built ' + buildPath[0])
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a large warning when signing is disabled so we are less likely to accidentally
|
||||
* ship unsigned binaries to users.
|
||||
*/
|
||||
function printWarning () {
|
||||
console.log(fs.readFileSync(path.join(__dirname, 'warning.txt'), 'utf8'))
|
||||
}
|
||||
|
||||
@@ -6,4 +6,6 @@ npm run update-authors
|
||||
git diff --exit-code
|
||||
rm -rf node_modules/
|
||||
npm install
|
||||
npm prune
|
||||
npm dedupe
|
||||
npm test
|
||||
|
||||
26
config.js
26
config.js
@@ -1,10 +1,13 @@
|
||||
var applicationConfigPath = require('application-config-path')
|
||||
var appConfig = require('application-config')('WebTorrent')
|
||||
var path = require('path')
|
||||
var pathExists = require('path-exists')
|
||||
|
||||
var APP_NAME = 'WebTorrent'
|
||||
var APP_TEAM = 'The WebTorrent Project'
|
||||
var APP_VERSION = require('./package.json').version
|
||||
|
||||
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
|
||||
|
||||
module.exports = {
|
||||
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
|
||||
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
|
||||
@@ -14,19 +17,20 @@ module.exports = {
|
||||
APP_VERSION: APP_VERSION,
|
||||
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
|
||||
|
||||
AUTO_UPDATE_CHECK_STARTUP_DELAY: 5 * 1000 /* 5 seconds */,
|
||||
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update' +
|
||||
'?version=' + APP_VERSION + '&platform=' + process.platform,
|
||||
AUTO_UPDATE_CHECK_STARTUP_DELAY: 5 * 1000 /* 5 seconds */,
|
||||
|
||||
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
||||
|
||||
CONFIG_PATH: applicationConfigPath(APP_NAME),
|
||||
CONFIG_POSTER_PATH: path.join(applicationConfigPath(APP_NAME), 'Posters'),
|
||||
CONFIG_TORRENT_PATH: path.join(applicationConfigPath(APP_NAME), 'Torrents'),
|
||||
CONFIG_PATH: getConfigPath(),
|
||||
CONFIG_POSTER_PATH: path.join(getConfigPath(), 'Posters'),
|
||||
CONFIG_TORRENT_PATH: path.join(getConfigPath(), 'Torrents'),
|
||||
|
||||
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop',
|
||||
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master',
|
||||
|
||||
IS_PORTABLE: isPortable(),
|
||||
IS_PRODUCTION: isProduction(),
|
||||
|
||||
ROOT_PATH: __dirname,
|
||||
@@ -40,6 +44,18 @@ module.exports = {
|
||||
WINDOW_MIN_WIDTH: 425
|
||||
}
|
||||
|
||||
function getConfigPath () {
|
||||
if (isPortable()) {
|
||||
return PORTABLE_PATH
|
||||
} else {
|
||||
return path.dirname(appConfig.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
function isPortable () {
|
||||
return process.platform === 'win32' && isProduction() && pathExists(PORTABLE_PATH)
|
||||
}
|
||||
|
||||
function isProduction () {
|
||||
if (!process.versions.electron) {
|
||||
return false
|
||||
|
||||
@@ -37,6 +37,10 @@ if (!shouldQuit) {
|
||||
}
|
||||
|
||||
function init () {
|
||||
if (config.IS_PORTABLE) {
|
||||
app.setPath('userData', config.CONFIG_PATH)
|
||||
}
|
||||
|
||||
app.ipcReady = false // main window has finished loading and IPC is ready
|
||||
app.isQuitting = false
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"airplay-js": "guerrerocarlos/node-airplay-js",
|
||||
"application-config": "^0.2.0",
|
||||
"application-config-path": "^0.1.0",
|
||||
"application-config": "feross/node-application-config",
|
||||
"bitfield": "^1.0.2",
|
||||
"chromecasts": "^1.8.0",
|
||||
"concat-stream": "^1.5.1",
|
||||
@@ -43,6 +42,7 @@
|
||||
"winreg": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-zip": "^1.0.0",
|
||||
"electron-osx-sign": "^0.3.0",
|
||||
"electron-packager": "^6.0.2",
|
||||
"electron-winstaller": "feross/windows-installer#build",
|
||||
@@ -50,6 +50,7 @@
|
||||
"minimist": "^1.2.0",
|
||||
"nobin-debian-installer": "^0.0.9",
|
||||
"plist": "^1.2.0",
|
||||
"run-series": "^1.1.4",
|
||||
"standard": "^6.0.5"
|
||||
},
|
||||
"homepage": "https://webtorrent.io",
|
||||
@@ -71,7 +72,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "node ./bin/clean.js",
|
||||
"package": "npm install && npm prune && npm dedupe && node ./bin/package.js",
|
||||
"package": "node ./bin/package.js",
|
||||
"start": "electron .",
|
||||
"test": "standard",
|
||||
"update-authors": "./bin/update-authors.sh"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
console.time('init')
|
||||
|
||||
var cfg = require('application-config')('WebTorrent')
|
||||
var appConfig = require('application-config')('WebTorrent')
|
||||
var concat = require('concat-stream')
|
||||
var dragDrop = require('drag-drop')
|
||||
var electron = require('electron')
|
||||
@@ -26,6 +26,8 @@ var util = require('./util')
|
||||
var {setDispatch} = require('./lib/dispatcher')
|
||||
setDispatch(dispatch)
|
||||
|
||||
appConfig.filePath = config.CONFIG_PATH + path.sep + 'config.json'
|
||||
|
||||
// Electron apps have two processes: a main process (node) runs first and starts
|
||||
// a renderer process (essentially a Chrome window). We're in the renderer process,
|
||||
// and this IPC channel receives from and sends messages to the main process
|
||||
@@ -425,9 +427,9 @@ function setupIpc () {
|
||||
|
||||
// Load state.saved from the JSON state file
|
||||
function loadState (cb) {
|
||||
cfg.read(function (err, data) {
|
||||
appConfig.read(function (err, data) {
|
||||
if (err) console.error(err)
|
||||
console.log('loaded state from ' + cfg.filePath)
|
||||
console.log('loaded state from ' + appConfig.filePath)
|
||||
|
||||
// populate defaults if they're not there
|
||||
state.saved = Object.assign({}, State.getDefaultSavedState(), data)
|
||||
@@ -457,7 +459,7 @@ function saveStateThrottled () {
|
||||
|
||||
// Write state.saved to the JSON state file
|
||||
function saveState () {
|
||||
console.log('saving state to ' + cfg.filePath)
|
||||
console.log('saving state to ' + appConfig.filePath)
|
||||
|
||||
// Clean up, so that we're not saving any pending state
|
||||
var copy = Object.assign({}, state.saved)
|
||||
@@ -479,7 +481,7 @@ function saveState () {
|
||||
return torrent
|
||||
})
|
||||
|
||||
cfg.write(copy, function (err) {
|
||||
appConfig.write(copy, function (err) {
|
||||
if (err) console.error(err)
|
||||
ipcRenderer.send('savedState')
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var electron = require('electron')
|
||||
var path = require('path')
|
||||
|
||||
var remote = electron.remote
|
||||
|
||||
@@ -255,6 +256,8 @@ function getDefaultSavedState () {
|
||||
]
|
||||
}
|
||||
],
|
||||
downloadPath: remote.app.getPath('downloads')
|
||||
downloadPath: config.IS_PORTABLE
|
||||
? path.join(config.CONFIG_PATH, 'Downloads')
|
||||
: remote.app.getPath('downloads')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user