electron-packager/common.js

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
}