index.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. 'use strict'
  2. const common = require('./common')
  3. const debug = require('debug')('electron-packager')
  4. const download = require('./download')
  5. const extract = require('extract-zip')
  6. const fs = require('fs-extra')
  7. const getMetadataFromPackageJSON = require('./infer')
  8. const hooks = require('./hooks')
  9. const ignore = require('./ignore')
  10. const metadata = require('./package.json')
  11. const nodeify = require('nodeify')
  12. const path = require('path')
  13. const pify = require('pify')
  14. const targets = require('./targets')
  15. function debugHostInfo () {
  16. debug(`Electron Packager ${metadata.version}`)
  17. debug(`Node ${process.version}`)
  18. debug(`Host Operating system: ${process.platform} (${process.arch})`)
  19. }
  20. class Packager {
  21. constructor (opts) {
  22. this.opts = opts
  23. this.tempBase = common.baseTempDir(opts)
  24. this.useTempDir = opts.tmpdir !== false
  25. this.canCreateSymlinks = undefined
  26. }
  27. ensureTempDir () {
  28. if (this.useTempDir) {
  29. return fs.remove(this.tempBase)
  30. } else {
  31. return Promise.resolve()
  32. }
  33. }
  34. testSymlink (comboOpts, zipPath) {
  35. const testPath = path.join(this.tempBase, 'symlink-test')
  36. const testFile = path.join(testPath, 'test')
  37. const testLink = path.join(testPath, 'testlink')
  38. const cleanup = (symlinksWork) =>
  39. fs.remove(testPath).then(() => symlinksWork)
  40. return fs.outputFile(testFile, '')
  41. .then(() => fs.symlink(testFile, testLink))
  42. .then(() => cleanup(true))
  43. .catch(/* istanbul ignore next */ () => cleanup(false))
  44. .then(result => {
  45. this.canCreateSymlinks = result
  46. if (this.canCreateSymlinks) return this.checkOverwrite(comboOpts, zipPath)
  47. /* istanbul ignore next */
  48. return this.skipHostPlatformSansSymlinkSupport(comboOpts)
  49. })
  50. }
  51. /* istanbul ignore next */
  52. skipHostPlatformSansSymlinkSupport (comboOpts) {
  53. common.info(`Cannot create symlinks (on Windows hosts, it requires admin privileges); skipping ${comboOpts.platform} platform`, this.opts.quiet)
  54. return Promise.resolve()
  55. }
  56. overwriteAndCreateApp (outDir, comboOpts, zipPath) {
  57. debug(`Removing ${outDir} due to setting overwrite: true`)
  58. return fs.remove(outDir)
  59. .then(() => this.createApp(comboOpts, zipPath))
  60. }
  61. extractElectronZip (comboOpts, zipPath, buildDir) {
  62. debug(`Extracting ${zipPath} to ${buildDir}`)
  63. return pify(extract)(zipPath, { dir: buildDir })
  64. .then(() => hooks.promisifyHooks(this.opts.afterExtract, [buildDir, comboOpts.electronVersion, comboOpts.platform, comboOpts.arch]))
  65. }
  66. createApp (comboOpts, zipPath) {
  67. let buildParentDir
  68. if (this.useTempDir) {
  69. buildParentDir = this.tempBase
  70. } else {
  71. buildParentDir = this.opts.out || process.cwd()
  72. }
  73. const buildDir = path.resolve(buildParentDir, `${comboOpts.platform}-${comboOpts.arch}-template`)
  74. common.info(`Packaging app for platform ${comboOpts.platform} ${comboOpts.arch} using electron v${comboOpts.electronVersion}`, this.opts.quiet)
  75. debug(`Creating ${buildDir}`)
  76. return fs.ensureDir(buildDir)
  77. .then(() => this.extractElectronZip(comboOpts, zipPath, buildDir))
  78. .then(() => {
  79. const os = require(targets.osModules[comboOpts.platform])
  80. const app = new os.App(comboOpts, buildDir)
  81. return app.create()
  82. })
  83. }
  84. checkOverwrite (comboOpts, zipPath) {
  85. const finalPath = common.generateFinalPath(comboOpts)
  86. return fs.pathExists(finalPath)
  87. .then(exists => {
  88. if (exists) {
  89. if (this.opts.overwrite) {
  90. return this.overwriteAndCreateApp(finalPath, comboOpts, zipPath)
  91. } else {
  92. common.info(`Skipping ${comboOpts.platform} ${comboOpts.arch} (output dir already exists, use --overwrite to force)`, this.opts.quiet)
  93. return true
  94. }
  95. } else {
  96. return this.createApp(comboOpts, zipPath)
  97. }
  98. })
  99. }
  100. packageForPlatformAndArch (downloadOpts) {
  101. return download.downloadElectronZip(downloadOpts)
  102. .then(zipPath => {
  103. // Create delegated options object with specific platform and arch, for output directory naming
  104. const comboOpts = Object.assign({}, this.opts, {
  105. arch: downloadOpts.arch,
  106. platform: downloadOpts.platform,
  107. electronVersion: downloadOpts.version
  108. })
  109. if (!this.useTempDir) {
  110. return this.createApp(comboOpts, zipPath)
  111. }
  112. if (common.isPlatformMac(comboOpts.platform)) {
  113. /* istanbul ignore else */
  114. if (this.canCreateSymlinks === undefined) {
  115. return this.testSymlink(comboOpts, zipPath)
  116. } else if (!this.canCreateSymlinks) {
  117. return this.skipHostPlatformSansSymlinkSupport(comboOpts)
  118. }
  119. }
  120. return this.checkOverwrite(comboOpts, zipPath)
  121. })
  122. }
  123. }
  124. function packageAllSpecifiedCombos (opts, archs, platforms) {
  125. const packager = new Packager(opts)
  126. return packager.ensureTempDir()
  127. .then(() => Promise.all(download.createDownloadCombos(opts, platforms, archs).map(
  128. downloadOpts => packager.packageForPlatformAndArch(downloadOpts)
  129. )))
  130. }
  131. function packagerPromise (opts) {
  132. debugHostInfo()
  133. if (debug.enabled) debug(`Packager Options: ${JSON.stringify(opts)}`)
  134. const archs = targets.validateListFromOptions(opts, 'arch')
  135. const platforms = targets.validateListFromOptions(opts, 'platform')
  136. if (!Array.isArray(archs)) return Promise.reject(archs)
  137. if (!Array.isArray(platforms)) return Promise.reject(platforms)
  138. debug(`Target Platforms: ${platforms.join(', ')}`)
  139. debug(`Target Architectures: ${archs.join(', ')}`)
  140. const packageJSONDir = path.resolve(process.cwd(), opts.dir) || process.cwd()
  141. return getMetadataFromPackageJSON(platforms, opts, packageJSONDir)
  142. .then(() => {
  143. if (opts.name.endsWith(' Helper')) {
  144. throw new Error('Application names cannot end in " Helper" due to limitations on macOS')
  145. }
  146. debug(`Application name: ${opts.name}`)
  147. debug(`Target Electron version: ${opts.electronVersion}`)
  148. ignore.generateIgnores(opts)
  149. return packageAllSpecifiedCombos(opts, archs, platforms)
  150. })
  151. // Remove falsy entries (e.g. skipped platforms)
  152. .then(appPaths => appPaths.filter(appPath => appPath))
  153. }
  154. module.exports = function packager (opts, cb) {
  155. return nodeify(packagerPromise(opts), cb)
  156. }