mirror of
https://github.com/Llewellynvdm/nativefier.git
synced 2024-12-23 10:38:55 +00:00
Merge pull request #88 from kfranqueiro/refactor
Implement --all, refactor code, add tests
This commit is contained in:
commit
8d26a51213
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,2 @@
|
|||||||
downloaded/*.zip
|
|
||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
test/dist
|
|
9
cli.js
9
cli.js
@ -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])
|
||||||
})
|
})
|
||||||
|
159
common.js
159
common.js
@ -1,50 +1,139 @@
|
|||||||
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 asar = require('asar')
|
||||||
|
var mkdirp = require('mkdirp')
|
||||||
|
var ncp = require('ncp').ncp
|
||||||
var rimraf = require('rimraf')
|
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 = {
|
module.exports = {
|
||||||
asarApp: function asarApp (finalDir, cb) {
|
generateFinalPath: generateFinalPath,
|
||||||
var src = path.join(finalDir, 'app')
|
|
||||||
var dest = path.join(finalDir, 'app.asar')
|
initializeApp: function initializeApp (opts, templatePath, appRelativePath, callback) {
|
||||||
asar.createPackage(src, dest, function (err) {
|
// Performs the following initial operations for an app:
|
||||||
if (err) return cb(err)
|
// * Creates temporary directory
|
||||||
rimraf(src, function (err) {
|
// * Copies template into temporary directory
|
||||||
if (err) return cb(err)
|
// * Copies user's app into temporary directory
|
||||||
cb(null, dest)
|
// * 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) {
|
moveApp: function finalizeApp (opts, tempPath, callback) {
|
||||||
if (opts.prune) {
|
var finalPath = generateFinalPath(opts)
|
||||||
child.exec('npm prune --production', { cwd: cwd }, function pruned (err) {
|
// Prefer ncp over mv (which seems to cause issues on Win8)
|
||||||
if (err) return cb(err)
|
series([
|
||||||
nextStep()
|
function (cb) {
|
||||||
})
|
mkdirp(finalPath, cb)
|
||||||
} else {
|
},
|
||||||
nextStep()
|
function (cb) {
|
||||||
}
|
ncp(tempPath, finalPath, cb)
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
callback(err, finalPath)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
userIgnoreFilter: function userIgnoreFilter (opts, finalDir) {
|
normalizeExt: function normalizeExt (filename, targetExt, cb) {
|
||||||
return function filter (file) {
|
// Forces a filename to a given extension and fires the given callback with the normalized filename,
|
||||||
if (path.sep === '\\') {
|
// if it exists. Otherwise reports the error from the fs.stat call.
|
||||||
// convert slashes so unix-format ignores work
|
// (Used for resolving icon filenames, particularly during --all runs.)
|
||||||
file = file.replace(/\\/g, '/')
|
|
||||||
}
|
|
||||||
|
|
||||||
var ignore = opts.ignore || []
|
// This error path is used by win32.js if no icon is specified
|
||||||
if (!Array.isArray(ignore)) ignore = [ignore]
|
if (!filename) return cb(new Error('No filename specified to normalizeExt'))
|
||||||
if (typeof finalDir !== 'undefined') {
|
|
||||||
ignore = ignore.concat([finalDir])
|
var ext = path.extname(filename)
|
||||||
}
|
if (ext !== targetExt) {
|
||||||
for (var i = 0; i < ignore.length; i++) {
|
filename = filename.slice(0, filename.length - ext.length) + targetExt
|
||||||
if (file.match(ignore[i])) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.stat(filename, function (err) {
|
||||||
|
cb(err, err ? null : filename)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
190
index.js
190
index.js
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
71
linux.js
71
linux.js
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
213
mac.js
213
mac.js
@ -1,160 +1,87 @@
|
|||||||
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
|
var appVersion = opts['app-version']
|
||||||
mkdirp(newApp, function mkdirpd () {
|
|
||||||
// ignore errors
|
appPlist.CFBundleDisplayName = opts.name
|
||||||
// copy .app folder and use as template (this is exactly what Atom editor does)
|
appPlist.CFBundleIdentifier = opts['app-bundle-id'] || defaultBundleName
|
||||||
ncp(electronApp, newApp, function copied (err) {
|
appPlist.CFBundleName = opts.name
|
||||||
if (err) return cb(err)
|
helperPlist.CFBundleIdentifier = opts['helper-bundle-id'] || defaultBundleName + '.helper'
|
||||||
buildMacApp(opts, cb, newApp)
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -26,18 +26,20 @@
|
|||||||
"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": {
|
||||||
|
"run-waterfall": "^1.1.1",
|
||||||
"standard": "^3.3.2",
|
"standard": "^3.3.2",
|
||||||
"tape": "^4.0.0"
|
"tape": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "standard && node test/test.js"
|
"test": "standard && tape test"
|
||||||
},
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"test/dist"
|
"test/fixtures/**/node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
readme.md
31
readme.md
@ -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*
|
||||||
|
273
test/basic.js
Normal file
273
test/basic.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
var fs = require('fs')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var packager = require('..')
|
||||||
|
var waterfall = require('run-waterfall')
|
||||||
|
|
||||||
|
var config = require('./config.json')
|
||||||
|
var util = require('./util')
|
||||||
|
|
||||||
|
function generateBasename (opts) {
|
||||||
|
return opts.name + '-' + opts.platform + '-' + opts.arch
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNamePath (opts) {
|
||||||
|
// Generates path to verify reflects the name given in the options.
|
||||||
|
// Returns the Helper.app location on darwin since the top-level .app is already tested for the resources path;
|
||||||
|
// returns the executable for other OSes
|
||||||
|
if (opts.platform === 'darwin') {
|
||||||
|
return path.join(opts.name + '.app', 'Contents', 'Frameworks', opts.name + ' Helper.app')
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts.name + (opts.platform === 'win32' ? '.exe' : '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDefaultsTest (combination) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = Object.create(combination)
|
||||||
|
opts.name = 'basicTest'
|
||||||
|
opts.dir = path.join(__dirname, 'fixtures', 'basic')
|
||||||
|
|
||||||
|
var finalPath
|
||||||
|
var resourcesPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
t.true(Array.isArray(paths), 'packager call should resolve to an array')
|
||||||
|
t.equal(paths.length, 1, 'Single-target run should resolve to a 1-item array')
|
||||||
|
|
||||||
|
finalPath = paths[0]
|
||||||
|
t.equal(finalPath, path.join(util.getWorkCwd(), generateBasename(opts)),
|
||||||
|
'Path should follow the expected format and be in the cwd')
|
||||||
|
fs.stat(finalPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The expected output directory should exist')
|
||||||
|
resourcesPath = path.join(finalPath, util.generateResourcesPath(opts))
|
||||||
|
fs.stat(path.join(finalPath, generateNamePath(opts)), cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
if (opts.platform === 'darwin') {
|
||||||
|
t.true(stats.isDirectory(), 'The Helper.app should reflect opts.name')
|
||||||
|
} else {
|
||||||
|
t.true(stats.isFile(), 'The executable should reflect opts.name')
|
||||||
|
}
|
||||||
|
fs.stat(resourcesPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory')
|
||||||
|
fs.stat(path.join(resourcesPath, 'app', 'node_modules', 'run-waterfall'), cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The output directory should contain devDependencies by default (no prune)')
|
||||||
|
util.areFilesEqual(path.join(opts.dir, 'main.js'), path.join(resourcesPath, 'app', 'main.js'), cb)
|
||||||
|
}, function (equal, cb) {
|
||||||
|
t.true(equal, 'File under packaged app directory should match source file')
|
||||||
|
util.areFilesEqual(path.join(opts.dir, 'ignore', 'this.txt'),
|
||||||
|
path.join(resourcesPath, 'app', 'ignore', 'this.txt'),
|
||||||
|
cb)
|
||||||
|
}, function (equal, cb) {
|
||||||
|
t.true(equal,
|
||||||
|
'File under subdirectory of packaged app directory should match source file and not be ignored by default')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOutTest (combination) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = Object.create(combination)
|
||||||
|
opts.name = 'basicTest'
|
||||||
|
opts.dir = path.join(__dirname, 'fixtures', 'basic')
|
||||||
|
opts.out = 'dist'
|
||||||
|
|
||||||
|
var finalPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
finalPath = paths[0]
|
||||||
|
t.equal(finalPath, path.join('dist', generateBasename(opts)),
|
||||||
|
'Path should follow the expected format and be under the folder specifed in `out`')
|
||||||
|
fs.stat(finalPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The expected output directory should exist')
|
||||||
|
fs.stat(path.join(finalPath, util.generateResourcesPath(opts)), cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAsarTest (combination) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = Object.create(combination)
|
||||||
|
opts.name = 'basicTest'
|
||||||
|
opts.dir = path.join(__dirname, 'fixtures', 'basic')
|
||||||
|
opts.asar = true
|
||||||
|
|
||||||
|
var finalPath
|
||||||
|
var resourcesPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
finalPath = paths[0]
|
||||||
|
fs.stat(finalPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The expected output directory should exist')
|
||||||
|
resourcesPath = path.join(finalPath, util.generateResourcesPath(opts))
|
||||||
|
fs.stat(resourcesPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory')
|
||||||
|
fs.stat(path.join(resourcesPath, 'app.asar'), cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isFile(), 'app.asar should exist under the resources subdirectory when opts.asar is true')
|
||||||
|
fs.exists(path.join(resourcesPath, 'app'), function (exists) {
|
||||||
|
t.false(exists, 'app subdirectory should NOT exist when app.asar is built')
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPruneTest (combination) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = Object.create(combination)
|
||||||
|
opts.name = 'basicTest'
|
||||||
|
opts.dir = path.join(__dirname, 'fixtures', 'basic')
|
||||||
|
opts.prune = true
|
||||||
|
|
||||||
|
var finalPath
|
||||||
|
var resourcesPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
finalPath = paths[0]
|
||||||
|
fs.stat(finalPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The expected output directory should exist')
|
||||||
|
resourcesPath = path.join(finalPath, util.generateResourcesPath(opts))
|
||||||
|
fs.stat(resourcesPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory')
|
||||||
|
fs.stat(path.join(resourcesPath, 'app', 'node_modules', 'run-series'), cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'npm dependency should exist under app/node_modules')
|
||||||
|
fs.exists(path.join(resourcesPath, 'app', 'node_modules', 'run-waterfall'), function (exists) {
|
||||||
|
t.false(exists, 'npm devDependency should NOT exist under app/node_modules')
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIgnoreTest (combination, ignorePattern, ignoredFile) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = Object.create(combination)
|
||||||
|
opts.name = 'basicTest'
|
||||||
|
opts.dir = path.join(__dirname, 'fixtures', 'basic')
|
||||||
|
opts.ignore = ignorePattern
|
||||||
|
|
||||||
|
var appPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
appPath = path.join(paths[0], util.generateResourcesPath(opts), 'app')
|
||||||
|
fs.stat(path.join(appPath, 'package.json'), cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isFile(), 'The expected output directory should exist and contain files')
|
||||||
|
fs.exists(path.join(appPath, ignoredFile), function (exists) {
|
||||||
|
t.false(exists, 'Ignored file should not exist in output app directory')
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOverwriteTest (combination) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout * 2) // Multiplied since this test packages the application twice
|
||||||
|
|
||||||
|
var opts = Object.create(combination)
|
||||||
|
opts.name = 'basicTest'
|
||||||
|
opts.dir = path.join(__dirname, 'fixtures', 'basic')
|
||||||
|
|
||||||
|
var finalPath
|
||||||
|
var testPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
finalPath = paths[0]
|
||||||
|
fs.stat(finalPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The expected output directory should exist')
|
||||||
|
// Create a dummy file to detect whether the output directory is replaced in subsequent runs
|
||||||
|
testPath = path.join(finalPath, 'test.txt')
|
||||||
|
fs.writeFile(testPath, 'test', cb)
|
||||||
|
}, function (cb) {
|
||||||
|
// Run again, defaulting to overwrite false
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
fs.stat(testPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isFile(), 'The existing output directory should exist as before (skipped by default)')
|
||||||
|
// Run a third time, explicitly setting overwrite to true
|
||||||
|
opts.overwrite = true
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
fs.exists(testPath, function (exists) {
|
||||||
|
t.false(exists, 'The output directory should be regenerated when overwrite is true')
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.testAllPlatforms('defaults test', createDefaultsTest)
|
||||||
|
util.testAllPlatforms('out test', createOutTest)
|
||||||
|
util.testAllPlatforms('asar test', createAsarTest)
|
||||||
|
util.testAllPlatforms('prune test', createPruneTest)
|
||||||
|
util.testAllPlatforms('ignore test: string in array', createIgnoreTest, ['ignorethis'], 'ignorethis.txt')
|
||||||
|
util.testAllPlatforms('ignore test: string', createIgnoreTest, 'ignorethis', 'ignorethis.txt')
|
||||||
|
util.testAllPlatforms('ignore test: RegExp', createIgnoreTest, /ignorethis/, 'ignorethis.txt')
|
||||||
|
util.testAllPlatforms('ignore test: string with slash', createIgnoreTest, 'ignore/this',
|
||||||
|
path.join('ignore', 'this.txt'))
|
||||||
|
util.testAllPlatforms('ignore test: only match subfolder of app', createIgnoreTest, 'electron-packager',
|
||||||
|
path.join('electron-packager', 'readme.txt'))
|
||||||
|
util.testAllPlatforms('overwrite test', createOverwriteTest)
|
4
test/config.json
Normal file
4
test/config.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"timeout": 15000,
|
||||||
|
"version": "0.28.3"
|
||||||
|
}
|
2
test/fixtures/basic/electron-packager/readme.txt
vendored
Normal file
2
test/fixtures/basic/electron-packager/readme.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
This file exists to test ability to ignore paths under app, without also
|
||||||
|
ignoring the entire app folder due to a match above it (#54 / #55).
|
0
test/fixtures/basic/ignore/this.txt
vendored
Normal file
0
test/fixtures/basic/ignore/this.txt
vendored
Normal file
1
test/fixtures/basic/ignorethis.txt
vendored
Normal file
1
test/fixtures/basic/ignorethis.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
4
test/fixtures/basic/index.html
vendored
Normal file
4
test/fixtures/basic/index.html
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>Hello, world!</body>
|
||||||
|
</html>
|
22
test/fixtures/basic/main.js
vendored
Normal file
22
test/fixtures/basic/main.js
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
var app = require('app')
|
||||||
|
var BrowserWindow = require('browser-window')
|
||||||
|
var mainWindow
|
||||||
|
|
||||||
|
app.on('window-all-closed', function () {
|
||||||
|
app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('ready', function () {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
center: true,
|
||||||
|
title: 'Basic Test',
|
||||||
|
width: 800,
|
||||||
|
height: 600
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.loadUrl('file://' + require('path').resolve(__dirname, 'index.html'))
|
||||||
|
|
||||||
|
mainWindow.on('closed', function () {
|
||||||
|
mainWindow = null
|
||||||
|
})
|
||||||
|
})
|
9
test/fixtures/basic/package.json
vendored
Normal file
9
test/fixtures/basic/package.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"main": "main.js",
|
||||||
|
"dependencies": {
|
||||||
|
"run-series": "^1.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"run-waterfall": "^1.1.1"
|
||||||
|
}
|
||||||
|
}
|
BIN
test/fixtures/monochrome.icns
vendored
Normal file
BIN
test/fixtures/monochrome.icns
vendored
Normal file
Binary file not shown.
27
test/index.js
Normal file
27
test/index.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
var exec = require('child_process').exec
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var series = require('run-series')
|
||||||
|
|
||||||
|
var config = require('./config.json')
|
||||||
|
var util = require('./util')
|
||||||
|
|
||||||
|
// Download all Electron distributions before running tests to avoid timing out due to network speed
|
||||||
|
series([
|
||||||
|
function (cb) {
|
||||||
|
console.log('Calling electron-download before running tests...')
|
||||||
|
util.downloadAll(config.version, cb)
|
||||||
|
}, function (cb) {
|
||||||
|
console.log('Running npm install in fixtures/basic...')
|
||||||
|
exec('npm install', {cwd: path.join(__dirname, 'fixtures', 'basic')}, cb)
|
||||||
|
}
|
||||||
|
], function () {
|
||||||
|
console.log('Running tests...')
|
||||||
|
require('./basic')
|
||||||
|
require('./multitarget')
|
||||||
|
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
// Perform additional tests specific to building for OS X
|
||||||
|
require('./mac')
|
||||||
|
}
|
||||||
|
})
|
94
test/mac.js
Normal file
94
test/mac.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
var exec = require('child_process').exec
|
||||||
|
var fs = require('fs')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var packager = require('..')
|
||||||
|
var test = require('tape')
|
||||||
|
var waterfall = require('run-waterfall')
|
||||||
|
|
||||||
|
var config = require('./config.json')
|
||||||
|
var util = require('./util')
|
||||||
|
|
||||||
|
function createIconTest (icon, iconPath) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: 'basicTest',
|
||||||
|
dir: path.join(__dirname, 'fixtures', 'basic'),
|
||||||
|
version: config.version,
|
||||||
|
arch: 'x64',
|
||||||
|
platform: 'darwin',
|
||||||
|
icon: icon
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourcesPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
resourcesPath = path.join(paths[0], util.generateResourcesPath(opts))
|
||||||
|
fs.stat(resourcesPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory')
|
||||||
|
util.areFilesEqual(iconPath, path.join(resourcesPath, 'atom.icns'), cb)
|
||||||
|
}, function (equal, cb) {
|
||||||
|
t.true(equal, 'atom.icns should be identical to the specified icon file')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconBase = path.join(__dirname, 'fixtures', 'monochrome')
|
||||||
|
var icnsPath = iconBase + '.icns'
|
||||||
|
util.setup()
|
||||||
|
test('icon test: .icns specified', createIconTest(icnsPath, icnsPath))
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('icon test: .ico specified (should replace with .icns)', createIconTest(iconBase + '.ico', icnsPath))
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('icon test: basename only (should add .icns)', createIconTest(iconBase, icnsPath))
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('codesign test', function (t) {
|
||||||
|
t.timeoutAfter(config.timeout)
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: 'basicTest',
|
||||||
|
dir: path.join(__dirname, 'fixtures', 'basic'),
|
||||||
|
version: config.version,
|
||||||
|
arch: 'x64',
|
||||||
|
platform: 'darwin',
|
||||||
|
sign: '-' // Ad-hoc
|
||||||
|
}
|
||||||
|
|
||||||
|
var appPath
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (paths, cb) {
|
||||||
|
appPath = path.join(paths[0], opts.name + '.app')
|
||||||
|
fs.stat(appPath, cb)
|
||||||
|
}, function (stats, cb) {
|
||||||
|
t.true(stats.isDirectory(), 'The expected .app directory should exist')
|
||||||
|
exec('codesign --verify --deep ' + appPath, cb)
|
||||||
|
}, function (stdout, stderr, cb) {
|
||||||
|
t.pass('codesign should verify successfully')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
var notFound = err && err.code === 127
|
||||||
|
if (notFound) console.log('codesign not installed; skipped')
|
||||||
|
t.end(notFound ? null : err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
util.teardown()
|
145
test/multitarget.js
Normal file
145
test/multitarget.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
var fs = require('fs')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var packager = require('..')
|
||||||
|
var series = require('run-series')
|
||||||
|
var test = require('tape')
|
||||||
|
var waterfall = require('run-waterfall')
|
||||||
|
|
||||||
|
var config = require('./config.json')
|
||||||
|
var util = require('./util')
|
||||||
|
|
||||||
|
function verifyPackageExistence (finalPaths, callback) {
|
||||||
|
series(finalPaths.map(function (finalPath) {
|
||||||
|
return function (cb) {
|
||||||
|
fs.stat(finalPath, cb)
|
||||||
|
}
|
||||||
|
}), function (err, statsResults) {
|
||||||
|
if (err) return callback(null, false)
|
||||||
|
|
||||||
|
callback(null, statsResults.every(function (stats) {
|
||||||
|
return stats.isDirectory()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('all test', function (t) {
|
||||||
|
t.timeoutAfter(config.timeout * 5) // 4-5 packages will be built during this test
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: 'basicTest',
|
||||||
|
dir: path.join(__dirname, 'fixtures', 'basic'),
|
||||||
|
version: config.version,
|
||||||
|
all: true
|
||||||
|
}
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (finalPaths, cb) {
|
||||||
|
// Windows skips packaging for OS X, and OS X only has 64-bit releases
|
||||||
|
t.equal(finalPaths.length, process.platform === 'win32' ? 4 : 5,
|
||||||
|
'packager call should resolve with expected number of paths')
|
||||||
|
verifyPackageExistence(finalPaths, cb)
|
||||||
|
}, function (exists, cb) {
|
||||||
|
t.true(exists, 'Packages should be generated for all possible platforms')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('platform=all test (one arch)', function (t) {
|
||||||
|
t.timeoutAfter(config.timeout * 2) // 2 packages will be built during this test
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: 'basicTest',
|
||||||
|
dir: path.join(__dirname, 'fixtures', 'basic'),
|
||||||
|
version: config.version,
|
||||||
|
arch: 'ia32',
|
||||||
|
platform: 'all'
|
||||||
|
}
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (finalPaths, cb) {
|
||||||
|
t.equal(finalPaths.length, 2, 'packager call should resolve with expected number of paths')
|
||||||
|
verifyPackageExistence(finalPaths, cb)
|
||||||
|
}, function (exists, cb) {
|
||||||
|
t.true(exists, 'Packages should be generated for both 32-bit platforms')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('arch=all test (one platform)', function (t) {
|
||||||
|
t.timeoutAfter(config.timeout * 2) // 2 packages will be built during this test
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: 'basicTest',
|
||||||
|
dir: path.join(__dirname, 'fixtures', 'basic'),
|
||||||
|
version: config.version,
|
||||||
|
arch: 'all',
|
||||||
|
platform: 'linux'
|
||||||
|
}
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (finalPaths, cb) {
|
||||||
|
t.equal(finalPaths.length, 2, 'packager call should resolve with expected number of paths')
|
||||||
|
verifyPackageExistence(finalPaths, cb)
|
||||||
|
}, function (exists, cb) {
|
||||||
|
t.true(exists, 'Packages should be generated for both architectures')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
function createMultiTest (arch, platform) {
|
||||||
|
return function (t) {
|
||||||
|
t.timeoutAfter(config.timeout * 4) // 4 packages will be built during this test
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: 'basicTest',
|
||||||
|
dir: path.join(__dirname, 'fixtures', 'basic'),
|
||||||
|
version: config.version,
|
||||||
|
arch: arch,
|
||||||
|
platform: platform
|
||||||
|
}
|
||||||
|
|
||||||
|
waterfall([
|
||||||
|
function (cb) {
|
||||||
|
packager(opts, cb)
|
||||||
|
}, function (finalPaths, cb) {
|
||||||
|
t.equal(finalPaths.length, 4, 'packager call should resolve with expected number of paths')
|
||||||
|
verifyPackageExistence(finalPaths, cb)
|
||||||
|
}, function (exists, cb) {
|
||||||
|
t.true(exists, 'Packages should be generated for all combinations of specified archs and platforms')
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('multi-platform / multi-arch test, from arrays', createMultiTest(['ia32', 'x64'], ['linux', 'win32']))
|
||||||
|
util.teardown()
|
||||||
|
|
||||||
|
util.setup()
|
||||||
|
test('multi-platform / multi-arch test, from strings', createMultiTest('ia32,x64', 'linux,win32'))
|
||||||
|
util.teardown()
|
45
test/test.js
45
test/test.js
@ -1,45 +0,0 @@
|
|||||||
var test = require('tape')
|
|
||||||
var mkdirp = require('mkdirp')
|
|
||||||
var rimraf = require('rimraf')
|
|
||||||
var packager = require('../index.js')
|
|
||||||
|
|
||||||
var distdir = __dirname + '/dist'
|
|
||||||
rimraf.sync(distdir)
|
|
||||||
mkdirp.sync(distdir)
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
dir: __dirname + '/testapp',
|
|
||||||
name: 'Test',
|
|
||||||
version: '0.28.2',
|
|
||||||
out: distdir
|
|
||||||
}
|
|
||||||
|
|
||||||
test('package for windows', function (t) {
|
|
||||||
opts.platform = 'win32'
|
|
||||||
opts.arch = 'ia32'
|
|
||||||
|
|
||||||
packager(opts, function done (err, appPath) {
|
|
||||||
t.notOk(err, 'no err')
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('package for linux', function (t) {
|
|
||||||
opts.platform = 'linux'
|
|
||||||
opts.arch = 'x64'
|
|
||||||
|
|
||||||
packager(opts, function done (err, appPath) {
|
|
||||||
t.notOk(err, 'no err')
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('package for darwin', function (t) {
|
|
||||||
opts.platform = 'darwin'
|
|
||||||
opts.arch = 'x64'
|
|
||||||
|
|
||||||
packager(opts, function done (err, appPath) {
|
|
||||||
t.notOk(err, 'no err')
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,6 +0,0 @@
|
|||||||
var app = require('app')
|
|
||||||
|
|
||||||
app.on('ready', function () {
|
|
||||||
console.log('pizza')
|
|
||||||
app.exit()
|
|
||||||
})
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "testapp",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "BSD-2-Clause"
|
|
||||||
}
|
|
100
test/util.js
Normal file
100
test/util.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
var fs = require('fs')
|
||||||
|
var path = require('path')
|
||||||
|
var test = require('tape')
|
||||||
|
|
||||||
|
var download = require('electron-download')
|
||||||
|
var mkdirp = require('mkdirp')
|
||||||
|
var rimraf = require('rimraf')
|
||||||
|
var series = require('run-series')
|
||||||
|
|
||||||
|
var ORIGINAL_CWD = process.cwd()
|
||||||
|
var WORK_CWD = path.join(__dirname, 'work')
|
||||||
|
|
||||||
|
var archs = ['ia32', 'x64']
|
||||||
|
var platforms = ['darwin', 'linux', 'win32']
|
||||||
|
var slice = Array.prototype.slice
|
||||||
|
var version = require('./config.json').version
|
||||||
|
|
||||||
|
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
|
||||||
|
// Also skip testing darwin target on Windows since electron-packager itself skips it
|
||||||
|
// (see https://github.com/maxogden/electron-packager/issues/71)
|
||||||
|
if (platform === 'darwin' && (arch === 'ia32' || require('os').platform() === 'win32')) return
|
||||||
|
|
||||||
|
combinations.push({
|
||||||
|
arch: arch,
|
||||||
|
platform: platform,
|
||||||
|
version: version
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
exports.areFilesEqual = function areFilesEqual (file1, file2, callback) {
|
||||||
|
series([
|
||||||
|
function (cb) {
|
||||||
|
fs.readFile(file1, cb)
|
||||||
|
},
|
||||||
|
function (cb) {
|
||||||
|
fs.readFile(file2, cb)
|
||||||
|
}
|
||||||
|
], function (err, buffers) {
|
||||||
|
callback(err, slice.call(buffers[0]).every(function (b, i) {
|
||||||
|
return b === buffers[1][i]
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.downloadAll = function downloadAll (version, callback) {
|
||||||
|
series(combinations.map(function (combination) {
|
||||||
|
return function (cb) {
|
||||||
|
download(combination, cb)
|
||||||
|
}
|
||||||
|
}), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.forEachCombination = function forEachCombination (cb) {
|
||||||
|
combinations.forEach(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.generateResourcesPath = function generateResourcesPath (opts) {
|
||||||
|
return opts.platform === 'darwin' ? path.join(opts.name + '.app', 'Contents', 'Resources') : 'resources'
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getWorkCwd = function getWorkCwd () {
|
||||||
|
return WORK_CWD
|
||||||
|
}
|
||||||
|
|
||||||
|
// tape doesn't seem to have a provision for before/beforeEach/afterEach/after,
|
||||||
|
// so run setup/teardown and cleanup tasks as additional "tests" to put them in sequence
|
||||||
|
// and run them irrespective of test failures
|
||||||
|
|
||||||
|
exports.setup = function setup () {
|
||||||
|
test('setup', function (t) {
|
||||||
|
mkdirp(WORK_CWD, function (err) {
|
||||||
|
if (err) t.end(err)
|
||||||
|
process.chdir(WORK_CWD)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.teardown = function teardown () {
|
||||||
|
test('teardown', function (t) {
|
||||||
|
process.chdir(ORIGINAL_CWD)
|
||||||
|
rimraf(WORK_CWD, function (err) {
|
||||||
|
t.end(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.testAllPlatforms = function testAllPlatforms (name, createTest /*, ...createTestArgs */) {
|
||||||
|
var args = slice.call(arguments, 2)
|
||||||
|
exports.setup()
|
||||||
|
exports.forEachCombination(function (combination) {
|
||||||
|
test(name + ': ' + combination.platform + '-' + combination.arch,
|
||||||
|
createTest.apply(null, [combination].concat(args)))
|
||||||
|
})
|
||||||
|
exports.teardown()
|
||||||
|
}
|
@ -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
104
win32.js
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user