2
2
mirror of https://github.com/Llewellynvdm/nativefier.git synced 2024-12-22 18:18:55 +00:00

Implement multi-target options and refactor code

This adds support for --all, --platform=all, and --arch=all.

In order to accommodate outputting multiple directories for multiple
platforms and architectures, this also implements a new directory
structure under the output folder (distinguished by both platform and
arch).  This structure is applied even to OS X distributions, which
formerly were output directly to an .app folder.  This could be considered
a backwards-incompatible change.

One other backwards-incompatible change is the value that the packager
function passes to the callback, which is now always an array of paths,
rather than just a single path.

The behavior of the icon option has also been modified to use its
basename and apply .ico or .icns depending on platform, to make it
usable with --all and --platform=all.  This attempts to maximize
backwards compatibility, by allowing a full filename to be specified,
but replacing the filename's extension with what is appropriate for
each target platform.  Alternatively, the extension can now be omitted.

In the process of implementing this, it became evident that some things
were being done in 3 different places, and weren't always being done
consistently, so I've deduplicated everything I could.

This also includes a few other changes to improve stability for
multi-target runs, and other fixes:

* Avoid targeting darwin if the environment doesn't support symlinks,
  to avoid the process bailing out on Windows
* Implement --overwrite centrally in index.js such that it explicitly
  skips if an output directory already exists, for consistency with
  all target platforms and to avoid any possible errors that would halt
  operation during one target of a multi-target run
* Use ncp instead of mv to move to finalPath, which avoids flakiness
  I noticed when testing on Windows 8 especially with multi-target runs
* Simplify temp directory logic by using a nested structure, so there
  is only one top-level directory to clean up
* Reinstate fix from #55 which seems to have been clobbered by a later
  merge
* linux.createApp now resolves to the final output directory;
  it was formerly resolving to the executable path
* mac.createApp now replaces space with underscore in bundle IDs
* Only the platform modules that are needed are loaded
* The win32 module only loads rcedit if needed

This also fixes a couple of missing updates to docs (readme/usage).

This commit addresses the following issues:

* Resolves #40
* Resolves #38
* Resolves #70
* Works around #71
* Resolves #84 by reinstating #55
This commit is contained in:
Kenneth G. Franqueiro 2015-06-19 22:36:15 -04:00
parent cff6ab50ee
commit a157f716a5
10 changed files with 421 additions and 377 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
downloaded/*.zip
node_modules node_modules
.DS_Store .DS_Store
test/dist test/dist

9
cli.js
View File

@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
var fs = require('fs') var fs = require('fs')
var args = require('minimist')(process.argv.slice(2), {boolean: ['prune', 'asar', 'overwrite']}) var args = require('minimist')(process.argv.slice(2), {boolean: ['prune', 'asar', 'all', 'overwrite']})
var packager = require('./') var packager = require('./')
var usage = fs.readFileSync(__dirname + '/usage.txt').toString() var usage = fs.readFileSync(__dirname + '/usage.txt').toString()
@ -16,17 +16,18 @@ if (protocolSchemes && protocolNames && protocolNames.length === protocolSchemes
}) })
} }
if (!args.dir || !args.name || !args.platform || !args.arch || !args.version) { if (!args.dir || !args.name || !args.version || (!args.all && (!args.platform || !args.arch))) {
console.error(usage) console.error(usage)
process.exit(1) process.exit(1)
} }
packager(args, function done (err, appPath) { packager(args, function done (err, appPaths) {
if (err) { if (err) {
if (err.message) console.error(err.message) if (err.message) console.error(err.message)
else console.error(err, err.stack) else console.error(err, err.stack)
process.exit(1) process.exit(1)
} }
console.error('Wrote new app to', appPath) if (appPaths.length > 1) console.error('Wrote new apps to:\n' + appPaths.join('\n'))
else if (appPaths.length === 1) console.error('Wrote new app to', appPaths[0])
}) })

131
common.js
View File

@ -1,12 +1,17 @@
var asar = require('asar')
var child = require('child_process') var child = require('child_process')
var fs = require('fs')
var os = require('os')
var path = require('path') var path = require('path')
var rimraf = require('rimraf')
module.exports = { var asar = require('asar')
asarApp: function asarApp (finalDir, cb) { var mkdirp = require('mkdirp')
var src = path.join(finalDir, 'app') var ncp = require('ncp').ncp
var dest = path.join(finalDir, 'app.asar') var rimraf = require('rimraf')
var series = require('run-series')
function asarApp (appPath, cb) {
var src = path.join(appPath)
var dest = path.join(appPath, '..', 'app.asar')
asar.createPackage(src, dest, function (err) { asar.createPackage(src, dest, function (err) {
if (err) return cb(err) if (err) return cb(err)
rimraf(src, function (err) { rimraf(src, function (err) {
@ -14,21 +19,20 @@ module.exports = {
cb(null, dest) cb(null, dest)
}) })
}) })
}, }
prune: function prune (opts, cwd, cb, nextStep) { function generateFinalBasename (opts) {
if (opts.prune) { return opts.name + '-' + opts.platform + '-' + opts.arch
child.exec('npm prune --production', { cwd: cwd }, function pruned (err) { }
if (err) return cb(err)
nextStep()
})
} else {
nextStep()
}
},
userIgnoreFilter: function userIgnoreFilter (opts, finalDir) { function generateFinalPath (opts) {
return path.join(opts.out || process.cwd(), generateFinalBasename(opts))
}
function userIgnoreFilter (opts) {
return function filter (file) { return function filter (file) {
file = file.split(path.resolve(opts.dir))[1]
if (path.sep === '\\') { if (path.sep === '\\') {
// convert slashes so unix-format ignores work // convert slashes so unix-format ignores work
file = file.replace(/\\/g, '/') file = file.replace(/\\/g, '/')
@ -36,9 +40,6 @@ module.exports = {
var ignore = opts.ignore || [] var ignore = opts.ignore || []
if (!Array.isArray(ignore)) ignore = [ignore] if (!Array.isArray(ignore)) ignore = [ignore]
if (typeof finalDir !== 'undefined') {
ignore = ignore.concat([finalDir])
}
for (var i = 0; i < ignore.length; i++) { for (var i = 0; i < ignore.length; i++) {
if (file.match(ignore[i])) { if (file.match(ignore[i])) {
return false return false
@ -46,5 +47,93 @@ module.exports = {
} }
return true return true
} }
}
module.exports = {
generateFinalPath: generateFinalPath,
initializeApp: function initializeApp (opts, templatePath, appRelativePath, callback) {
// Performs the following initial operations for an app:
// * Creates temporary directory
// * Copies template into temporary directory
// * Copies user's app into temporary directory
// * Prunes non-production node_modules (if opts.prune is set)
// * Creates an asar (if opts.asar is set)
var tempParent = path.join(os.tmpdir(), 'electron-packager', opts.platform + '-' + opts.arch)
var tempPath = path.join(tempParent, generateFinalBasename(opts))
// Path to `app` directory
var appPath = path.join(tempPath, appRelativePath)
var operations = [
function (cb) {
rimraf(tempParent, function () {
// Ignore errors (e.g. directory didn't exist anyway)
cb()
})
},
function (cb) {
mkdirp(tempPath, cb)
},
function (cb) {
ncp(templatePath, tempPath, cb)
},
function (cb) {
ncp(opts.dir, appPath, {filter: userIgnoreFilter(opts), dereference: true}, cb)
}
]
// Prune and asar are now performed before platform-specific logic, primarily so that
// appPath is predictable (e.g. before .app is renamed for mac)
if (opts.prune) {
operations.push(function (cb) {
child.exec('npm prune --production', {cwd: appPath}, cb)
})
}
if (opts.asar) {
operations.push(function (cb) {
asarApp(path.join(appPath), cb)
})
}
series(operations, function (err) {
if (err) return callback(err)
// Resolve to path to temporary app folder for platform-specific processes to use
callback(null, tempPath)
})
},
moveApp: function finalizeApp (opts, tempPath, callback) {
var finalPath = generateFinalPath(opts)
// Prefer ncp over mv (which seems to cause issues on Win8)
series([
function (cb) {
mkdirp(finalPath, cb)
},
function (cb) {
ncp(tempPath, finalPath, cb)
}
], function (err) {
callback(err, finalPath)
})
},
normalizeExt: function normalizeExt (filename, targetExt, cb) {
// Forces a filename to a given extension and fires the given callback with the normalized filename,
// if it exists. Otherwise reports the error from the fs.stat call.
// (Used for resolving icon filenames, particularly during --all runs.)
// This error path is used by win32.js if no icon is specified
if (!filename) return cb(new Error('No filename specified to normalizeExt'))
var ext = path.extname(filename)
if (ext !== targetExt) {
filename = filename.slice(0, filename.length - ext.length) + targetExt
}
fs.stat(filename, function (err) {
cb(err, err ? null : filename)
})
} }
} }

190
index.js
View File

@ -1,59 +1,169 @@
var path = require('path') var path = require('path')
var fs = require('fs')
var os = require('os') var os = require('os')
var download = require('electron-download') var download = require('electron-download')
var extract = require('extract-zip') var extract = require('extract-zip')
var mkdirp = require('mkdirp') var mkdirp = require('mkdirp')
var rimraf = require('rimraf') var rimraf = require('rimraf')
var series = require('run-series')
var common = require('./common')
var mac = require('./mac.js') var supportedArchs = {
var linux = require('./linux.js') ia32: 1,
var win32 = require('./win32.js') x64: 1
}
var supportedPlatforms = {
// Maps to module ID for each platform (lazy-required if used)
darwin: './mac',
linux: './linux',
win32: './win32'
}
var tempBase = path.join(os.tmpdir(), 'electron-packager')
function testSymlink (cb) {
var testPath = path.join(tempBase, 'symlink-test')
var testFile = path.join(testPath, 'test')
var testLink = path.join(testPath, 'testlink')
series([
function (cb) {
mkdirp(testPath, cb)
},
function (cb) {
fs.writeFile(testFile, '', cb)
},
function (cb) {
fs.symlink(testFile, testLink, cb)
}
], function (err) {
var result = !err
rimraf(testPath, function () {
cb(result) // ignore errors on cleanup
})
})
}
function validateList (list, supported, name) {
// Validates list of architectures or platforms.
// Returns a normalized array if successful, or an error message string otherwise.
if (!list) return 'Must specify ' + name
if (list === 'all') return Object.keys(supported)
if (!Array.isArray(list)) list = list.split(',')
for (var i = list.length; i--;) {
if (!supported[list[i]]) {
return 'Unsupported ' + name + ' ' + list[i] + '; must be one of: ' + Object.keys(supported).join(', ')
}
}
return list
}
function createSeries (opts, archs, platforms) {
var combinations = []
archs.forEach(function (arch) {
platforms.forEach(function (platform) {
// Electron does not have 32-bit releases for Mac OS X, so skip that combination
if (platform === 'darwin' && arch === 'ia32') return
combinations.push({
platform: platform,
arch: arch,
version: opts.version
})
})
})
return [
function (cb) {
rimraf(tempBase, cb)
}
].concat(combinations.map(function (combination) {
var arch = combination.arch
var platform = combination.platform
var version = combination.version
return function (callback) {
download(combination, function (err, zipPath) {
if (err) return callback(err)
var tmpDir = path.join(tempBase, platform + '-' + arch + '-template')
var operations = [
function (cb) {
mkdirp(tmpDir, cb)
},
function (cb) {
extract(zipPath, {dir: tmpDir}, cb)
}
]
function createApp (comboOpts) {
console.error('Packaging app for platform', platform + ' ' + arch, 'using electron v' + version)
series(operations, function () {
require(supportedPlatforms[platform]).createApp(comboOpts, tmpDir, callback)
})
}
function checkOverwrite () {
// Create delegated options object with specific platform and arch, for output directory naming
var comboOpts = Object.create(opts)
comboOpts.arch = arch
comboOpts.platform = platform
var finalPath = common.generateFinalPath(comboOpts)
fs.exists(finalPath, function (exists) {
if (exists) {
if (opts.overwrite) {
rimraf(finalPath, function () {
createApp(comboOpts)
})
} else {
console.error('Skipping ' + platform + ' ' + arch +
' (output dir already exists, use --overwrite to force)')
callback()
}
} else {
createApp(comboOpts)
}
})
}
if (combination.platform === 'darwin') {
testSymlink(function (result) {
if (result) return checkOverwrite()
console.error('Cannot create symlinks; skipping darwin platform')
callback()
})
} else {
checkOverwrite()
}
})
}
}))
}
module.exports = function packager (opts, cb) { module.exports = function packager (opts, cb) {
var platformPackager var archs = validateList(opts.all ? 'all' : opts.arch, supportedArchs, 'arch')
var platform = opts.platform var platforms = validateList(opts.all ? 'all' : opts.platform, supportedPlatforms, 'platform')
var arch = opts.arch if (!opts.version) return cb(new Error('Must specify version'))
var version = opts.version if (!Array.isArray(archs)) return cb(new Error(archs))
if (!Array.isArray(platforms)) return cb(new Error(platforms))
if (!platform || !arch || !version) cb(new Error('Must specify platform, arch and version'))
switch (arch) {
case 'ia32': break
case 'x64': break
default: return cb(new Error('Unsupported arch. Must be either ia32 or x64'))
}
switch (platform) {
case 'darwin': platformPackager = mac; break
case 'linux': platformPackager = linux; break
case 'win32': platformPackager = win32; break
default: return cb(new Error('Unsupported platform. Must be either darwin, linux, or win32'))
}
// Ignore this and related modules by default // Ignore this and related modules by default
var defaultIgnores = ['/node_modules/electron-prebuilt($|/)', '/node_modules/electron-packager($|/)', '/\.git($|/)'] var defaultIgnores = ['/node_modules/electron-prebuilt($|/)', '/node_modules/electron-packager($|/)', '/\.git($|/)']
if (opts.ignore && !Array.isArray(opts.ignore)) opts.ignore = [opts.ignore] if (opts.ignore && !Array.isArray(opts.ignore)) opts.ignore = [opts.ignore]
opts.ignore = (opts.ignore) ? opts.ignore.concat(defaultIgnores) : defaultIgnores opts.ignore = (opts.ignore) ? opts.ignore.concat(defaultIgnores) : defaultIgnores
download({ series(createSeries(opts, archs, platforms), function (err, appPaths) {
platform: platform,
arch: arch,
version: version
}, function (err, zipPath) {
if (err) return cb(err) if (err) return cb(err)
console.error('Packaging app for platform', platform + ' ' + arch, 'using electron v' + version)
// extract zip into tmp so that packager can use it as a template cb(null, appPaths.filter(function (appPath) {
var tmpDir = path.join(os.tmpdir(), 'electron-packager-' + platform + '-template') // Remove falsy entries (e.g. skipped platforms)
rimraf(tmpDir, function (err) { return appPath
if (err) {} // ignore err }))
mkdirp(tmpDir, function (err) {
if (err) return cb(err)
extract(zipPath, {dir: tmpDir}, function (err) {
if (err) return cb(err)
platformPackager.createApp(opts, tmpDir, cb)
})
})
})
}) })
} }

View File

@ -1,70 +1,15 @@
var path = require('path') var path = require('path')
var fs = require('fs') var mv = require('mv')
var mkdirp = require('mkdirp')
var ncp = require('ncp').ncp
var rimraf = require('rimraf')
var common = require('./common') var common = require('./common')
module.exports = { module.exports = {
createApp: function createApp (opts, templateApp, cb) { createApp: function createApp (opts, templatePath, callback) {
var finalDir = path.join(opts.out || process.cwd(), opts.name + '-linux') common.initializeApp(opts, templatePath, path.join('resources', 'app'), function buildLinuxApp (err, tempPath) {
var userAppDir = path.join(finalDir, 'resources', 'default_app') if (err) return callback(err)
var originalBinary = path.join(finalDir, 'electron') mv(path.join(tempPath, 'electron'), path.join(tempPath, opts.name), function (err) {
var finalBinary = path.join(finalDir, opts.name) if (err) return callback(err)
common.moveApp(opts, tempPath, callback)
function copyApp () {
var createApp = function (err) {
if (err) return cb(err)
mkdirp(finalDir, function AppFolderCreated (err) {
if (err) return cb(err)
copyAppTemplate()
}) })
}
if (opts.overwrite) {
fs.exists(finalDir, function (exists) {
if (exists) {
console.log('Overwriting existing ' + finalDir + ' ...')
rimraf(finalDir, createApp)
} else {
createApp()
}
}) })
} else {
createApp()
}
}
function copyAppTemplate () {
ncp(templateApp, finalDir, {filter: appFilter}, function AppCreated (err) {
if (err) return cb(err)
copyUserApp()
})
}
function copyUserApp () {
ncp(opts.dir, userAppDir, {filter: common.userIgnoreFilter(opts, finalDir), dereference: true}, function copied (err) {
if (err) return cb(err)
common.prune(opts, userAppDir, cb, renameElectronBinary)
})
}
function renameElectronBinary () {
fs.rename(originalBinary, finalBinary, function electronRenamed (err) {
var asarDir
if (err) return cb(err)
if (opts.asar) {
asarDir = path.join(finalDir, 'resources')
common.asarApp(asarDir, cb)
} else {
cb(null, finalBinary)
}
})
}
function appFilter (file) {
return file.match(/default_app/) === null
}
copyApp()
} }
} }

165
mac.js
View File

@ -1,65 +1,42 @@
var os = require('os')
var path = require('path') var path = require('path')
var fs = require('fs') var fs = require('fs')
var child = require('child_process') var child = require('child_process')
var plist = require('plist') var plist = require('plist')
var mkdirp = require('mkdirp')
var rimraf = require('rimraf')
var mv = require('mv') var mv = require('mv')
var ncp = require('ncp').ncp var ncp = require('ncp').ncp
var series = require('run-series')
var common = require('./common') var common = require('./common')
module.exports = { module.exports = {
createApp: function createApp (opts, electronPath, cb) { createApp: function createApp (opts, templatePath, callback) {
var electronApp = path.join(electronPath, 'Electron.app') var appRelativePath = path.join('Electron.app', 'Contents', 'Resources', 'app')
var tmpDir = path.join(os.tmpdir(), 'electron-packager-mac') common.initializeApp(opts, templatePath, appRelativePath, function buildMacApp (err, tempPath) {
if (err) return callback(err)
var newApp = path.join(tmpDir, opts.name + '.app') var contentsPath = path.join(tempPath, 'Electron.app', 'Contents')
var helperPath = path.join(contentsPath, 'Frameworks', 'Electron Helper.app')
var appPlistFilename = path.join(contentsPath, 'Info.plist')
var helperPlistFilename = path.join(helperPath, 'Contents', 'Info.plist')
var appPlist = plist.parse(fs.readFileSync(appPlistFilename).toString())
var helperPlist = plist.parse(fs.readFileSync(helperPlistFilename).toString())
// reset build folders + copy template app // Update plist files
rimraf(tmpDir, function rmrfd () { var defaultBundleName = 'com.electron.' + opts.name.toLowerCase().replace(/ /g, '_')
// ignore errors
mkdirp(newApp, function mkdirpd () {
// ignore errors
// copy .app folder and use as template (this is exactly what Atom editor does)
ncp(electronApp, newApp, function copied (err) {
if (err) return cb(err)
buildMacApp(opts, cb, newApp)
})
})
})
}
}
function buildMacApp (opts, cb, newApp) {
var paths = {
info1: path.join(newApp, 'Contents', 'Info.plist'),
info2: path.join(newApp, 'Contents', 'Frameworks', 'Electron Helper.app', 'Contents', 'Info.plist'),
app: path.join(newApp, 'Contents', 'Resources', 'app'),
helper: path.join(newApp, 'Contents', 'Frameworks', 'Electron Helper.app')
}
// update plist files
var pl1 = plist.parse(fs.readFileSync(paths.info1).toString())
var pl2 = plist.parse(fs.readFileSync(paths.info2).toString())
var bundleId = opts['app-bundle-id'] || 'com.electron.' + opts.name.toLowerCase()
var bundleHelperId = opts['helper-bundle-id'] || 'com.electron.' + opts.name.toLowerCase() + '.helper'
var appVersion = opts['app-version'] var appVersion = opts['app-version']
pl1.CFBundleDisplayName = opts.name appPlist.CFBundleDisplayName = opts.name
pl1.CFBundleIdentifier = bundleId appPlist.CFBundleIdentifier = opts['app-bundle-id'] || defaultBundleName
pl1.CFBundleName = opts.name appPlist.CFBundleName = opts.name
pl2.CFBundleIdentifier = bundleHelperId helperPlist.CFBundleIdentifier = opts['helper-bundle-id'] || defaultBundleName + '.helper'
pl2.CFBundleName = opts.name helperPlist.CFBundleName = opts.name
if (appVersion) { if (appVersion) {
pl1.CFBundleVersion = appVersion appPlist.CFBundleVersion = appVersion
} }
if (opts.protocols) { if (opts.protocols) {
pl2.CFBundleURLTypes = pl1.CFBundleURLTypes = opts.protocols.map(function (protocol) { helperPlist.CFBundleURLTypes = appPlist.CFBundleURLTypes = opts.protocols.map(function (protocol) {
return { return {
CFBundleURLName: protocol.name, CFBundleURLName: protocol.name,
CFBundleURLSchemes: [].concat(protocol.schemes) CFBundleURLSchemes: [].concat(protocol.schemes)
@ -67,94 +44,44 @@ function buildMacApp (opts, cb, newApp) {
}) })
} }
fs.writeFileSync(paths.info1, plist.build(pl1)) fs.writeFileSync(appPlistFilename, plist.build(appPlist))
fs.writeFileSync(paths.info2, plist.build(pl2)) fs.writeFileSync(helperPlistFilename, plist.build(helperPlist))
// copy users app into .app var operations = []
ncp(opts.dir, paths.app, {filter: common.userIgnoreFilter(opts), dereference: true}, function copied (err) {
if (err) return cb(err)
function moveHelper () { if (opts.icon) {
// Move helper binary before moving the parent helper app directory itself operations.push(function (cb) {
var helperDestination = path.join(path.dirname(paths.helper), opts.name + ' Helper.app') common.normalizeExt(opts.icon, '.icns', function (err, icon) {
var helperBinary = path.join(paths.helper, 'Contents', 'MacOS', 'Electron Helper') if (err) {
var helperBinaryDestination = path.join(path.dirname(helperBinary), opts.name + ' Helper') // Ignore error if icon doesn't exist, in case it's only available for other OS
cb(null)
fs.rename(helperBinary, helperBinaryDestination, function (err) {
if (err) return cb(err)
fs.rename(paths.helper, helperDestination, function (err) {
if (err) return cb(err)
moveApp()
})
})
}
function moveApp () {
// finally, move app into cwd
var outdir = opts.out || process.cwd()
var finalPath = path.join(outdir, opts.name + '.app')
mkdirp(outdir, function mkoutdirp () {
if (err) return cb(err)
if (opts.overwrite) {
fs.exists(finalPath, function (exists) {
if (exists) {
console.log('Overwriting existing ' + finalPath + ' ...')
rimraf(finalPath, deploy)
} else { } else {
deploy() ncp(icon, path.join(contentsPath, 'Resources', 'atom.icns'), cb)
} }
}) })
} else {
deploy()
}
function deploy (err) {
if (err) return cb(err)
mv(newApp, finalPath, function moved (err) {
if (err) return cb(err)
if (opts.asar) {
var finalPath = path.join(opts.out || process.cwd(), opts.name + '.app', 'Contents', 'Resources')
common.asarApp(finalPath, function (err) {
if (err) return cb(err)
updateMacIcon(function (err) {
if (err) return cb(err)
codesign()
})
})
} else {
updateMacIcon(function (err) {
if (err) return cb(err)
codesign()
})
}
})
}
}) })
} }
function updateMacIcon (cb) { // Move Helper binary, then Helper.app, then top-level .app
var finalPath = path.join(opts.out || process.cwd(), opts.name + '.app') var finalAppPath = path.join(tempPath, opts.name + '.app')
operations.push(function (cb) {
var helperBinaryPath = path.join(helperPath, 'Contents', 'MacOS')
mv(path.join(helperBinaryPath, 'Electron Helper'), path.join(helperBinaryPath, opts.name + ' Helper'), cb)
}, function (cb) {
mv(helperPath, path.join(path.dirname(helperPath), opts.name + ' Helper.app'), cb)
}, function (cb) {
mv(path.dirname(contentsPath), finalAppPath, cb)
})
if (!opts.icon) { if (opts.sign) {
return cb(null) operations.push(function (cb) {
} child.exec('codesign --deep --force --sign "' + opts.sign + '" ' + finalAppPath, cb)
ncp(opts.icon, path.join(finalPath, 'Contents', 'Resources', 'atom.icns'), function copied (err) {
cb(err)
}) })
} }
function codesign () { series(operations, function () {
var appPath = path.join(opts.out || process.cwd(), opts.name + '.app') common.moveApp(opts, tempPath, callback)
})
if (!opts.sign) return cb(null, appPath)
child.exec('codesign --deep --force --sign "' + opts.sign + '" ' + appPath, function (err, stdout, stderr) {
cb(err, appPath)
}) })
} }
common.prune(opts, paths.app, cb, moveHelper)
})
} }

View File

@ -26,7 +26,8 @@
"ncp": "^2.0.0", "ncp": "^2.0.0",
"plist": "^1.1.0", "plist": "^1.1.0",
"rcedit": "^0.3.0", "rcedit": "^0.3.0",
"rimraf": "^2.3.2" "rimraf": "^2.3.2",
"run-series": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"standard": "^3.3.2", "standard": "^3.3.2",

View File

@ -25,14 +25,15 @@ Usage: electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arc
Required options Required options
platform linux, win32, darwin platform all, or one or more of: linux, win32, darwin (comma-delimited if multiple)
arch ia32, x64 arch all, ia32, x64
version see https://github.com/atom/electron/releases version see https://github.com/atom/electron/releases
Example electron-packager ./ FooBar --platform=darwin --arch=x64 --version=0.25.1 Example electron-packager ./ FooBar --platform=darwin --arch=x64 --version=0.25.1
Optional options Optional options
all equivalent to --platform=all --arch=all
out the dir to put the app into at the end. defaults to current working dir out the dir to put the app into at the end. defaults to current working dir
icon the icon file to use as the icon for the app icon the icon file to use as the icon for the app
app-bundle-id bundle identifier to use in the app plist app-bundle-id bundle identifier to use in the app plist
@ -40,6 +41,7 @@ app-version version to set for the app
helper-bundle-id bundle identifier to use in the app helper plist helper-bundle-id bundle identifier to use in the app helper plist
ignore do not copy files into App whose filenames regex .match this string ignore do not copy files into App whose filenames regex .match this string
prune runs `npm prune --production` on the app prune runs `npm prune --production` on the app
overwrite if output directory for a platform already exists, replaces it rather than skipping it
asar packages the source code within your app into an archive asar packages the source code within your app into an archive
sign should contain the identity to be used when running `codesign` (OS X only) sign should contain the identity to be used when running `codesign` (OS X only)
version-string should contain a hash of the application metadata to be embedded into the executable (Windows only). Keys supported version-string should contain a hash of the application metadata to be embedded into the executable (Windows only). Keys supported
@ -58,7 +60,7 @@ version-string should contain a hash of the application metadata to be embed
This will: This will:
- Find or download the correct release of Electron - Find or download the correct release of Electron
- Use that version of electron to create a app in `<out>/<appname>-<platform>` - Use that version of electron to create a app in `<out>/<appname>-<platform>-<arch>`
You should be able to launch the app on the platform you built for. If not, check your settings and try again. You should be able to launch the app on the platform you built for. If not, check your settings and try again.
@ -80,15 +82,21 @@ The source directory.
The application name. The application name.
`platform` - *String* `platform` - *String*
Allowed values: *linux, win32, darwin* Allowed values: *linux, win32, darwin, all*
Not required if `all` is used.
Arbitrary combinations of individual platforms are also supported via a comma-delimited string or array of strings.
`arch` - *String* `arch` - *String*
Allowed values: *ia32, x64* Allowed values: *ia32, x64, all*
Not required if `all` is used.
`version` - *String* `version` - *String*
Electron version (without the 'v'). See https://github.com/atom/electron/releases Electron version (without the 'v'). See https://github.com/atom/electron/releases
**Optional** **Optional**
`all` - *Boolean*
Shortcut for `--arch=all --platform=all`
`out` - *String* `out` - *String*
`icon` - *String* `icon` - *String*
@ -103,10 +111,23 @@ Electron version (without the 'v'). See https://github.com/atom/electron/release
`prune` - *Boolean* `prune` - *Boolean*
`overwrite` - *Boolean*
`asar` - *Boolean* `asar` - *Boolean*
`sign` - *String* `sign` - *String*
`version-string` - *Object*
Object hash of application metadata to embed into the executable (Windows only):
* `CompanyName`
* `LegalCopyright`
* `FileDescription`
* `OriginalFilename`
* `FileVersion`
* `ProductVersion`
* `ProductName`
* `InternalName`
##### callback ##### callback
`err` - *Error* `err` - *Error*

View File

@ -2,14 +2,15 @@ Usage: electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arc
Required options Required options
platform linux, win32, darwin platform all, or one or more of: linux, win32, darwin (comma-delimited if multiple)
arch ia32, x64 arch all, ia32, x64
version see https://github.com/atom/electron/releases version see https://github.com/atom/electron/releases
Example electron-packager ./ FooBar --platform=darwin --arch=x64 --version=0.25.1 Example electron-packager ./ FooBar --platform=darwin --arch=x64 --version=0.28.2
Optional options Optional options
all equivalent to --platform=all --arch=all
out the dir to put the app into at the end. defaults to current working dir out the dir to put the app into at the end. defaults to current working dir
icon the icon file to use as the icon for the app icon the icon file to use as the icon for the app
app-bundle-id bundle identifier to use in the app plist app-bundle-id bundle identifier to use in the app plist
@ -17,4 +18,6 @@ app-version version to set for the app
helper-bundle-id bundle identifier to use in the app helper plist helper-bundle-id bundle identifier to use in the app helper plist
ignore do not copy files into App whose filenames regex .match this string ignore do not copy files into App whose filenames regex .match this string
prune runs `npm prune --production` on the app prune runs `npm prune --production` on the app
overwrite if output directory for a platform already exists, replaces it rather than skipping it
asar packages the source code within your app into an archive asar packages the source code within your app into an archive
sign should contain the identity to be used when running `codesign` (OS X only)

104
win32.js
View File

@ -1,92 +1,40 @@
var os = require('os')
var path = require('path') var path = require('path')
var mkdirp = require('mkdirp')
var rimraf = require('rimraf')
var ncp = require('ncp').ncp
var mv = require('mv') var mv = require('mv')
var rcedit = require('rcedit') var series = require('run-series')
var common = require('./common') var common = require('./common')
module.exports = { module.exports = {
createApp: function createApp (opts, electronApp, cb) { createApp: function createApp (opts, templatePath, callback) {
var tmpDir = path.join(os.tmpdir(), 'electron-packager-windows') common.initializeApp(opts, templatePath, path.join('resources', 'app'), function buildWinApp (err, tempPath) {
if (err) return callback(err)
var newApp = path.join(tmpDir, opts.name + '-win32') var newExePath = path.join(tempPath, opts.name + '.exe')
// reset build folders + copy template app var operations = [
rimraf(tmpDir, function rmrfd () { function (cb) {
// ignore errors mv(path.join(tempPath, 'electron.exe'), newExePath, cb)
mkdirp(newApp, function mkdirpd () { }
// ignore errors ]
// copy app folder and use as template (this is exactly what Atom editor does)
ncp(electronApp, newApp, function copied (err) {
if (err) return cb(err)
// rename electron.exe
mv(path.join(newApp, 'electron.exe'), path.join(newApp, opts.name + '.exe'), function (err) {
if (err) return cb(err)
buildWinApp(opts, cb, newApp) if (opts.icon || opts['version-string']) {
operations.push(function (cb) {
common.normalizeExt(opts.icon, '.ico', function (err, icon) {
var rcOpts = {}
if (opts['version-string']) rcOpts['version-string'] = opts['version-string']
// Icon might be omitted or only exist in one OS's format, so skip it if normalizeExt reports an error
if (!err) {
rcOpts.icon = icon
}
require('rcedit')(newExePath, rcOpts, cb)
}) })
}) })
}
series(operations, function () {
common.moveApp(opts, tempPath, callback)
}) })
}) })
} }
} }
function copy (from, to, cb) {
rimraf(to, function () {
mkdirp(to, function () {
ncp(from, to, function (err) {
if (err) { return cb(err) }
cb()
})
})
})
}
function buildWinApp (opts, cb, newApp) {
var paths = {
app: path.join(newApp, 'resources', 'app')
}
// copy users app into destination path
ncp(opts.dir, paths.app, {filter: common.userIgnoreFilter(opts), dereference: true}, function copied (err) {
if (err) return cb(err)
function moveApp () {
// finally, move app into cwd
var finalPath = path.join(opts.out || process.cwd(), opts.name + '-win32')
copy(newApp, finalPath, function moved (err) {
if (err) return cb(err)
if (opts.asar) {
var finalPath = path.join(opts.out || process.cwd(), opts.name + '-win32', 'resources')
common.asarApp(finalPath, function (err) {
if (err) return cb(err)
updateResourceData()
})
} else {
updateResourceData()
}
})
}
function updateResourceData () {
var finalPath = path.join(opts.out || process.cwd(), opts.name + '-win32')
if (!opts.icon && !opts['version-string']) {
return cb(null, finalPath)
}
var exePath = path.join(opts.out || process.cwd(), opts.name + '-win32', opts.name + '.exe')
var rcOptions = {
icon: opts.icon,
'version-string': opts['version-string']
}
rcedit(exePath, rcOptions, function (err) {
cb(err, finalPath)
})
}
common.prune(opts, paths.app, cb, moveApp)
})
}