diff --git a/.gitignore b/.gitignore index 7e95763..53d98e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -downloaded/*.zip node_modules .DS_Store test/dist \ No newline at end of file diff --git a/cli.js b/cli.js index a01e653..9bb0c4f 100755 --- a/cli.js +++ b/cli.js @@ -1,6 +1,6 @@ #!/usr/bin/env node 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 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) process.exit(1) } -packager(args, function done (err, appPath) { +packager(args, function done (err, appPaths) { if (err) { if (err.message) console.error(err.message) else console.error(err, err.stack) 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]) }) diff --git a/common.js b/common.js index 2d194a0..84ccd7f 100644 --- a/common.js +++ b/common.js @@ -1,50 +1,139 @@ -var asar = require('asar') var child = require('child_process') +var fs = require('fs') +var os = require('os') var path = require('path') + +var asar = require('asar') +var mkdirp = require('mkdirp') +var ncp = require('ncp').ncp 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) { + if (err) return cb(err) + rimraf(src, function (err) { + if (err) return cb(err) + cb(null, dest) + }) + }) +} + +function generateFinalBasename (opts) { + return opts.name + '-' + opts.platform + '-' + opts.arch +} + +function generateFinalPath (opts) { + return path.join(opts.out || process.cwd(), generateFinalBasename(opts)) +} + +function userIgnoreFilter (opts) { + return function filter (file) { + file = file.split(path.resolve(opts.dir))[1] + + if (path.sep === '\\') { + // convert slashes so unix-format ignores work + file = file.replace(/\\/g, '/') + } + + var ignore = opts.ignore || [] + if (!Array.isArray(ignore)) ignore = [ignore] + for (var i = 0; i < ignore.length; i++) { + if (file.match(ignore[i])) { + return false + } + } + return true + } +} module.exports = { - asarApp: function asarApp (finalDir, cb) { - var src = path.join(finalDir, 'app') - var dest = path.join(finalDir, 'app.asar') - asar.createPackage(src, dest, function (err) { - if (err) return cb(err) - rimraf(src, function (err) { - if (err) return cb(err) - cb(null, dest) + 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) }) }, - prune: function prune (opts, cwd, cb, nextStep) { - if (opts.prune) { - child.exec('npm prune --production', { cwd: cwd }, function pruned (err) { - if (err) return cb(err) - nextStep() - }) - } else { - nextStep() - } + 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) + }) }, - userIgnoreFilter: function userIgnoreFilter (opts, finalDir) { - return function filter (file) { - if (path.sep === '\\') { - // convert slashes so unix-format ignores work - file = file.replace(/\\/g, '/') - } + 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.) - var ignore = opts.ignore || [] - if (!Array.isArray(ignore)) ignore = [ignore] - if (typeof finalDir !== 'undefined') { - ignore = ignore.concat([finalDir]) - } - for (var i = 0; i < ignore.length; i++) { - if (file.match(ignore[i])) { - return false - } - } - return true + // 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) + }) } } diff --git a/index.js b/index.js index 6d5ed16..b506245 100644 --- a/index.js +++ b/index.js @@ -1,59 +1,169 @@ var path = require('path') +var fs = require('fs') var os = require('os') var download = require('electron-download') var extract = require('extract-zip') var mkdirp = require('mkdirp') var rimraf = require('rimraf') +var series = require('run-series') +var common = require('./common') -var mac = require('./mac.js') -var linux = require('./linux.js') -var win32 = require('./win32.js') +var supportedArchs = { + ia32: 1, + 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) { - var platformPackager - var platform = opts.platform - var arch = opts.arch - var version = opts.version - - 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')) - } + var archs = validateList(opts.all ? 'all' : opts.arch, supportedArchs, 'arch') + var platforms = validateList(opts.all ? 'all' : opts.platform, supportedPlatforms, 'platform') + if (!opts.version) return cb(new Error('Must specify version')) + if (!Array.isArray(archs)) return cb(new Error(archs)) + if (!Array.isArray(platforms)) return cb(new Error(platforms)) // Ignore this and related modules by default var defaultIgnores = ['/node_modules/electron-prebuilt($|/)', '/node_modules/electron-packager($|/)', '/\.git($|/)'] if (opts.ignore && !Array.isArray(opts.ignore)) opts.ignore = [opts.ignore] opts.ignore = (opts.ignore) ? opts.ignore.concat(defaultIgnores) : defaultIgnores - download({ - platform: platform, - arch: arch, - version: version - }, function (err, zipPath) { + series(createSeries(opts, archs, platforms), function (err, appPaths) { 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 - var tmpDir = path.join(os.tmpdir(), 'electron-packager-' + platform + '-template') - rimraf(tmpDir, function (err) { - 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) - }) - }) - }) + + cb(null, appPaths.filter(function (appPath) { + // Remove falsy entries (e.g. skipped platforms) + return appPath + })) }) } diff --git a/linux.js b/linux.js index 29c6f24..5e05ae1 100644 --- a/linux.js +++ b/linux.js @@ -1,70 +1,15 @@ var path = require('path') -var fs = require('fs') -var mkdirp = require('mkdirp') -var ncp = require('ncp').ncp -var rimraf = require('rimraf') +var mv = require('mv') var common = require('./common') module.exports = { - createApp: function createApp (opts, templateApp, cb) { - var finalDir = path.join(opts.out || process.cwd(), opts.name + '-linux') - var userAppDir = path.join(finalDir, 'resources', 'default_app') - var originalBinary = path.join(finalDir, 'electron') - var finalBinary = path.join(finalDir, opts.name) - - 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() + createApp: function createApp (opts, templatePath, callback) { + common.initializeApp(opts, templatePath, path.join('resources', 'app'), function buildLinuxApp (err, tempPath) { + if (err) return callback(err) + mv(path.join(tempPath, 'electron'), path.join(tempPath, opts.name), function (err) { + if (err) return callback(err) + common.moveApp(opts, tempPath, callback) }) - } - - 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() + }) } } diff --git a/mac.js b/mac.js index e541750..6045488 100644 --- a/mac.js +++ b/mac.js @@ -1,160 +1,87 @@ -var os = require('os') var path = require('path') var fs = require('fs') var child = require('child_process') var plist = require('plist') -var mkdirp = require('mkdirp') -var rimraf = require('rimraf') var mv = require('mv') var ncp = require('ncp').ncp +var series = require('run-series') var common = require('./common') module.exports = { - createApp: function createApp (opts, electronPath, cb) { - var electronApp = path.join(electronPath, 'Electron.app') - var tmpDir = path.join(os.tmpdir(), 'electron-packager-mac') + createApp: function createApp (opts, templatePath, callback) { + var appRelativePath = path.join('Electron.app', 'Contents', 'Resources', 'app') + 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 - rimraf(tmpDir, function rmrfd () { - // 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) + // Update plist files + var defaultBundleName = 'com.electron.' + opts.name.toLowerCase().replace(/ /g, '_') + var appVersion = opts['app-version'] + + appPlist.CFBundleDisplayName = opts.name + appPlist.CFBundleIdentifier = opts['app-bundle-id'] || defaultBundleName + appPlist.CFBundleName = opts.name + helperPlist.CFBundleIdentifier = opts['helper-bundle-id'] || defaultBundleName + '.helper' + helperPlist.CFBundleName = opts.name + + if (appVersion) { + appPlist.CFBundleVersion = appVersion + } + + if (opts.protocols) { + helperPlist.CFBundleURLTypes = appPlist.CFBundleURLTypes = opts.protocols.map(function (protocol) { + return { + CFBundleURLName: protocol.name, + CFBundleURLSchemes: [].concat(protocol.schemes) + } }) + } + + fs.writeFileSync(appPlistFilename, plist.build(appPlist)) + fs.writeFileSync(helperPlistFilename, plist.build(helperPlist)) + + var operations = [] + + if (opts.icon) { + operations.push(function (cb) { + common.normalizeExt(opts.icon, '.icns', function (err, icon) { + if (err) { + // Ignore error if icon doesn't exist, in case it's only available for other OS + cb(null) + } else { + ncp(icon, path.join(contentsPath, 'Resources', 'atom.icns'), cb) + } + }) + }) + } + + // Move Helper binary, then Helper.app, then top-level .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.sign) { + operations.push(function (cb) { + child.exec('codesign --deep --force --sign "' + opts.sign + '" ' + finalAppPath, cb) + }) + } + + series(operations, function () { + common.moveApp(opts, tempPath, callback) }) }) } } - -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'] - - pl1.CFBundleDisplayName = opts.name - pl1.CFBundleIdentifier = bundleId - pl1.CFBundleName = opts.name - pl2.CFBundleIdentifier = bundleHelperId - pl2.CFBundleName = opts.name - - if (appVersion) { - pl1.CFBundleVersion = appVersion - } - - if (opts.protocols) { - pl2.CFBundleURLTypes = pl1.CFBundleURLTypes = opts.protocols.map(function (protocol) { - return { - CFBundleURLName: protocol.name, - CFBundleURLSchemes: [].concat(protocol.schemes) - } - }) - } - - fs.writeFileSync(paths.info1, plist.build(pl1)) - fs.writeFileSync(paths.info2, plist.build(pl2)) - - // copy users app into .app - ncp(opts.dir, paths.app, {filter: common.userIgnoreFilter(opts), dereference: true}, function copied (err) { - if (err) return cb(err) - - function moveHelper () { - // Move helper binary before moving the parent helper app directory itself - var helperDestination = path.join(path.dirname(paths.helper), opts.name + ' Helper.app') - var helperBinary = path.join(paths.helper, 'Contents', 'MacOS', 'Electron Helper') - var helperBinaryDestination = path.join(path.dirname(helperBinary), opts.name + ' Helper') - - 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 { - deploy() - } - }) - } 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) { - var finalPath = path.join(opts.out || process.cwd(), opts.name + '.app') - - if (!opts.icon) { - return cb(null) - } - - ncp(opts.icon, path.join(finalPath, 'Contents', 'Resources', 'atom.icns'), function copied (err) { - cb(err) - }) - } - - function codesign () { - var appPath = path.join(opts.out || process.cwd(), opts.name + '.app') - - 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) - }) -} diff --git a/package.json b/package.json index 0a28a60..1628657 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "ncp": "^2.0.0", "plist": "^1.1.0", "rcedit": "^0.3.0", - "rimraf": "^2.3.2" + "rimraf": "^2.3.2", + "run-series": "^1.1.1" }, "devDependencies": { "standard": "^3.3.2", diff --git a/readme.md b/readme.md index fe74fc4..cbe362b 100644 --- a/readme.md +++ b/readme.md @@ -25,14 +25,15 @@ Usage: electron-packager --platform= --arch=/-` +- Use that version of electron to create a app in `/--` 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. `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* -Allowed values: *ia32, x64* +Allowed values: *ia32, x64, all* +Not required if `all` is used. `version` - *String* Electron version (without the 'v'). See https://github.com/atom/electron/releases **Optional** +`all` - *Boolean* +Shortcut for `--arch=all --platform=all` + `out` - *String* `icon` - *String* @@ -103,10 +111,23 @@ Electron version (without the 'v'). See https://github.com/atom/electron/release `prune` - *Boolean* +`overwrite` - *Boolean* + `asar` - *Boolean* `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 `err` - *Error* @@ -121,4 +142,4 @@ If you run this on windows and you want to set the icon for your app using the ` ### related -- [grunt-electron](https://github.com/sindresorhus/grunt-electron) - grunt plugin for electron-packager +- [grunt-electron](https://github.com/sindresorhus/grunt-electron) - grunt plugin for electron-packager \ No newline at end of file diff --git a/usage.txt b/usage.txt index fdc3c85..ef068c2 100644 --- a/usage.txt +++ b/usage.txt @@ -1,15 +1,16 @@ Usage: electron-packager --platform= --arch= --version= - + Required options -platform linux, win32, darwin -arch ia32, x64 +platform all, or one or more of: linux, win32, darwin (comma-delimited if multiple) +arch all, ia32, x64 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 +all equivalent to --platform=all --arch=all 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 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 ignore do not copy files into App whose filenames regex .match this string prune runs `npm prune --production` on the app -asar packages the source code within your app into an archive \ No newline at end of file +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 +sign should contain the identity to be used when running `codesign` (OS X only) \ No newline at end of file diff --git a/win32.js b/win32.js index 68cfe4d..0f7abaa 100644 --- a/win32.js +++ b/win32.js @@ -1,92 +1,40 @@ -var os = require('os') var path = require('path') -var mkdirp = require('mkdirp') -var rimraf = require('rimraf') -var ncp = require('ncp').ncp var mv = require('mv') -var rcedit = require('rcedit') +var series = require('run-series') var common = require('./common') module.exports = { - createApp: function createApp (opts, electronApp, cb) { - var tmpDir = path.join(os.tmpdir(), 'electron-packager-windows') + createApp: function createApp (opts, templatePath, callback) { + 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') - // reset build folders + copy template app - rimraf(tmpDir, function rmrfd () { - // 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) - // rename electron.exe - mv(path.join(newApp, 'electron.exe'), path.join(newApp, opts.name + '.exe'), function (err) { - if (err) return cb(err) + var newExePath = path.join(tempPath, opts.name + '.exe') + var operations = [ + function (cb) { + mv(path.join(tempPath, 'electron.exe'), newExePath, cb) + } + ] - 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) - }) -}