354 строки
11 KiB
JavaScript
354 строки
11 KiB
JavaScript
'use strict'
|
|
|
|
const asar = require('asar')
|
|
const debug = require('debug')('electron-packager')
|
|
const download = require('electron-download')
|
|
const fs = require('fs-extra')
|
|
const ignore = require('./ignore')
|
|
const minimist = require('minimist')
|
|
const os = require('os')
|
|
const path = require('path')
|
|
const pruneModules = require('./prune').pruneModules
|
|
const sanitize = require('sanitize-filename')
|
|
const semver = require('semver')
|
|
const series = require('run-series')
|
|
|
|
const archs = ['ia32', 'x64', 'armv7l']
|
|
const platforms = ['darwin', 'linux', 'mas', 'win32']
|
|
|
|
function parseCLIArgs (argv) {
|
|
var args = minimist(argv, {
|
|
boolean: [
|
|
'all',
|
|
'deref-symlinks',
|
|
'download.strictSSL',
|
|
'overwrite',
|
|
'prune',
|
|
'quiet'
|
|
],
|
|
default: {
|
|
'deref-symlinks': true,
|
|
'download.strictSSL': true,
|
|
prune: true
|
|
},
|
|
string: [
|
|
'electron-version',
|
|
'out'
|
|
]
|
|
})
|
|
|
|
args.dir = args._[0]
|
|
args.name = args._[1]
|
|
|
|
// Transform hyphenated keys into camelCase
|
|
module.exports.camelCase(args, false)
|
|
|
|
var protocolSchemes = [].concat(args.protocol || [])
|
|
var protocolNames = [].concat(args.protocolName || [])
|
|
|
|
if (protocolSchemes && protocolNames && protocolNames.length === protocolSchemes.length) {
|
|
args.protocols = protocolSchemes.map(function (scheme, i) {
|
|
return {schemes: [scheme], name: protocolNames[i]}
|
|
})
|
|
}
|
|
|
|
if (args.out === '') {
|
|
args.out = null
|
|
}
|
|
|
|
// Overrides for multi-typed arguments, because minimist doesn't support it
|
|
|
|
// asar: `Object` or `true`
|
|
if (args.asar === 'true' || args.asar instanceof Array) {
|
|
args.asar = true
|
|
}
|
|
|
|
// osx-sign: `Object` or `true`
|
|
if (args.osxSign === 'true') {
|
|
args.osxSign = true
|
|
}
|
|
|
|
// tmpdir: `String` or `false`
|
|
if (args.tmpdir === 'false') {
|
|
args.tmpdir = false
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
function asarApp (appPath, asarOptions, cb) {
|
|
var dest = path.join(appPath, '..', 'app.asar')
|
|
debug(`Running asar with the options ${JSON.stringify(asarOptions)}`)
|
|
asar.createPackageWithOptions(appPath, dest, asarOptions, function (err) {
|
|
if (err) return cb(err)
|
|
fs.remove(appPath, function (err) {
|
|
if (err) return cb(err)
|
|
cb(null, dest)
|
|
})
|
|
})
|
|
}
|
|
|
|
function isPlatformMac (platform) {
|
|
return platform === 'darwin' || platform === 'mas'
|
|
}
|
|
|
|
function sanitizeAppName (name) {
|
|
return sanitize(name, {replacement: '-'})
|
|
}
|
|
|
|
function generateFinalBasename (opts) {
|
|
return `${sanitizeAppName(opts.name)}-${opts.platform}-${opts.arch}`
|
|
}
|
|
|
|
function generateFinalPath (opts) {
|
|
return path.join(opts.out || process.cwd(), generateFinalBasename(opts))
|
|
}
|
|
|
|
function info (message, quiet) {
|
|
if (!quiet) {
|
|
console.error(message)
|
|
}
|
|
}
|
|
|
|
function warning (message, quiet) {
|
|
if (!quiet) {
|
|
console.warn(`WARNING: ${message}`)
|
|
}
|
|
}
|
|
|
|
function subOptionWarning (properties, optionName, parameter, value, quiet) {
|
|
if (properties.hasOwnProperty(parameter)) {
|
|
warning(`${optionName}.${parameter} will be inferred from the main options`, quiet)
|
|
}
|
|
properties[parameter] = value
|
|
}
|
|
|
|
function baseTempDir (opts) {
|
|
return path.join(opts.tmpdir || os.tmpdir(), 'electron-packager')
|
|
}
|
|
|
|
function createAsarOpts (opts) {
|
|
let asarOptions
|
|
if (opts.asar === true) {
|
|
asarOptions = {}
|
|
} else if (typeof opts.asar === 'object') {
|
|
asarOptions = opts.asar
|
|
} else if (opts.asar === false || opts.asar === undefined) {
|
|
return false
|
|
} else {
|
|
warning(`asar parameter set to an invalid value (${opts.asar}), ignoring and disabling asar`)
|
|
return false
|
|
}
|
|
|
|
return asarOptions
|
|
}
|
|
|
|
function createDownloadOpts (opts, platform, arch) {
|
|
let downloadOpts = Object.assign({}, opts.download)
|
|
|
|
subOptionWarning(downloadOpts, 'download', 'platform', platform, opts.quiet)
|
|
subOptionWarning(downloadOpts, 'download', 'arch', arch, opts.quiet)
|
|
subOptionWarning(downloadOpts, 'download', 'version', opts.electronVersion, opts.quiet)
|
|
|
|
return downloadOpts
|
|
}
|
|
|
|
module.exports = {
|
|
archs: archs,
|
|
platforms: platforms,
|
|
|
|
parseCLIArgs: parseCLIArgs,
|
|
|
|
isPlatformMac: isPlatformMac,
|
|
|
|
subOptionWarning: subOptionWarning,
|
|
|
|
baseTempDir: baseTempDir,
|
|
|
|
createAsarOpts: createAsarOpts,
|
|
createDownloadOpts: createDownloadOpts,
|
|
createDownloadCombos: function createDownloadCombos (opts, selectedPlatforms, selectedArchs, ignoreFunc) {
|
|
let combinations = []
|
|
for (let arch of selectedArchs) {
|
|
for (let platform of selectedPlatforms) {
|
|
// Electron does not have 32-bit releases for Mac OS X, so skip that combination
|
|
if (isPlatformMac(platform) && arch === 'ia32') continue
|
|
// Electron only has armv7l releases for Linux
|
|
if (arch === 'armv7l' && platform !== 'linux') continue
|
|
if (typeof ignoreFunc === 'function' && ignoreFunc(platform, arch)) continue
|
|
combinations.push(createDownloadOpts(opts, platform, arch))
|
|
}
|
|
}
|
|
|
|
return combinations
|
|
},
|
|
|
|
deprecatedParameter: function deprecatedParameter (properties, oldName, newName, newCLIName) {
|
|
if (properties.hasOwnProperty(oldName)) {
|
|
warning(`The ${oldName} parameter is deprecated, use ${newName} (or --${newCLIName} in the CLI) instead`)
|
|
if (!properties.hasOwnProperty(newName)) {
|
|
properties[newName] = properties[oldName]
|
|
}
|
|
delete properties[oldName]
|
|
}
|
|
},
|
|
|
|
kebabProperties: {
|
|
'electron-version': 'electronVersion',
|
|
'app-copyright': 'appCopyright',
|
|
'app-version': 'appVersion',
|
|
'build-version': 'buildVersion',
|
|
'package-manager': 'packageManager',
|
|
'app-bundle-id': 'appBundleId',
|
|
'app-category-type': 'appCategoryType',
|
|
'extend-info': 'extendInfo',
|
|
'extra-resource': 'extraResource',
|
|
'helper-bundle-id': 'helperBundleId',
|
|
'osx-sign': 'osxSign',
|
|
'protocol-name': 'protocolName'
|
|
},
|
|
|
|
camelCase: function camelCase (properties, warn) {
|
|
Object.keys(module.exports.kebabProperties).forEach(function (key) {
|
|
var value = module.exports.kebabProperties[key]
|
|
if (properties.hasOwnProperty(key)) {
|
|
if (warn) {
|
|
warning(`The ${key} parameter is deprecated when used from JS, use ${value} instead. It will be removed in the next major version.`)
|
|
}
|
|
if (!properties.hasOwnProperty(value)) {
|
|
properties[value] = properties[key]
|
|
}
|
|
delete properties[key]
|
|
}
|
|
})
|
|
},
|
|
|
|
downloadElectronZip: function downloadElectronZip (downloadOpts, cb) {
|
|
// armv7l builds have only been backfilled for Electron >= 1.0.0.
|
|
// See: https://github.com/electron/electron/pull/6986
|
|
if (downloadOpts.arch === 'armv7l' && semver.lt(downloadOpts.version, '1.0.0')) {
|
|
downloadOpts.arch = 'arm'
|
|
}
|
|
debug(`Downloading Electron with options ${JSON.stringify(downloadOpts)}`)
|
|
download(downloadOpts, cb)
|
|
},
|
|
|
|
generateFinalBasename: generateFinalBasename,
|
|
generateFinalPath: generateFinalPath,
|
|
|
|
info: info,
|
|
|
|
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 either truthy or undefined)
|
|
// * Creates an asar (if opts.asar is set)
|
|
|
|
var tempPath
|
|
if (opts.tmpdir === false) {
|
|
tempPath = generateFinalPath(opts)
|
|
} else {
|
|
tempPath = path.join(baseTempDir(opts), `${opts.platform}-${opts.arch}`, generateFinalBasename(opts))
|
|
}
|
|
|
|
debug(`Initializing app in ${tempPath} from ${templatePath} template`)
|
|
|
|
// Path to `app` directory
|
|
var appPath = path.join(tempPath, appRelativePath)
|
|
var resourcesPath = path.resolve(appPath, '..')
|
|
|
|
var operations = [
|
|
function (cb) {
|
|
fs.move(templatePath, tempPath, {clobber: true}, cb)
|
|
},
|
|
function (cb) {
|
|
// `deref-symlinks` is the default value so we'll use it unless
|
|
// `derefSymlinks` is defined.
|
|
var shouldDeref = opts['deref-symlinks']
|
|
if (opts.derefSymlinks !== undefined) {
|
|
shouldDeref = opts.derefSymlinks
|
|
}
|
|
|
|
fs.copy(opts.dir, appPath, {filter: ignore.userIgnoreFilter(opts), dereference: shouldDeref}, cb)
|
|
},
|
|
function (cb) {
|
|
var afterCopyHooks = (opts.afterCopy || []).map(function (afterCopyFn) {
|
|
return function (cb) {
|
|
afterCopyFn(appPath, opts.electronVersion, opts.platform, opts.arch, cb)
|
|
}
|
|
})
|
|
series(afterCopyHooks, cb)
|
|
},
|
|
function (cb) {
|
|
// Support removing old default_app folder that is now an asar archive
|
|
fs.remove(path.join(resourcesPath, 'default_app'), cb)
|
|
},
|
|
function (cb) {
|
|
fs.remove(path.join(resourcesPath, 'default_app.asar'), 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 || opts.prune === undefined) {
|
|
operations.push(function (cb) {
|
|
pruneModules(opts, appPath, cb)
|
|
})
|
|
}
|
|
|
|
let asarOptions = createAsarOpts(opts)
|
|
if (asarOptions) {
|
|
operations.push(function (cb) {
|
|
asarApp(appPath, asarOptions, cb)
|
|
})
|
|
}
|
|
|
|
series(operations, function (err) {
|
|
if (err) return callback(err)
|
|
// Resolve to path to temporary app folder for platform-specific processes to use
|
|
callback(null, tempPath)
|
|
})
|
|
},
|
|
|
|
moveApp: function finalizeApp (opts, tempPath, callback) {
|
|
var finalPath = generateFinalPath(opts)
|
|
|
|
if (opts.tmpdir === false) {
|
|
callback(null, finalPath)
|
|
return
|
|
}
|
|
|
|
debug(`Moving ${tempPath} to ${finalPath}`)
|
|
fs.move(tempPath, finalPath, function (err) {
|
|
callback(err, finalPath)
|
|
})
|
|
},
|
|
|
|
normalizeExt: function normalizeExt (filename, targetExt, cb) {
|
|
// Forces a filename to a given extension and fires the given callback with the normalized filename,
|
|
// if it exists. Otherwise reports the error from the fs.stat call.
|
|
// (Used for resolving icon filenames, particularly during --all runs.)
|
|
|
|
// This error path is used by win32.js if no icon is specified
|
|
if (!filename) return cb(new Error('No filename specified to normalizeExt'))
|
|
|
|
var ext = path.extname(filename)
|
|
if (ext !== targetExt) {
|
|
filename = filename.slice(0, filename.length - ext.length) + targetExt
|
|
}
|
|
|
|
fs.stat(filename, function (err) {
|
|
cb(err, err ? null : filename)
|
|
})
|
|
},
|
|
|
|
rename: function rename (basePath, oldName, newName, cb) {
|
|
debug(`Renaming ${oldName} to ${newName} in ${basePath}`)
|
|
fs.rename(path.join(basePath, oldName), path.join(basePath, newName), cb)
|
|
},
|
|
sanitizeAppName: sanitizeAppName,
|
|
warning: warning
|
|
}
|