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 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') var newApp = path.join(tmpDir, opts.name + '.app') // 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) }) }) }) } } 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') } // 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 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, moveApp) }) }