basic.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. 'use strict'
  2. const common = require('../common')
  3. const download = require('../download')
  4. const fs = require('fs-extra')
  5. const hostArch = require('../targets').hostArch
  6. const packager = require('..')
  7. const path = require('path')
  8. const test = require('ava')
  9. const util = require('./_util')
  10. // Generates a path to the generated app that reflects the name given in the options.
  11. // Returns the Helper.app location on darwin since the top-level .app is already tested for the
  12. // resources path; on other OSes, returns the executable.
  13. function generateNamePath (opts) {
  14. if (common.isPlatformMac(opts.platform)) {
  15. return path.join(`${opts.name}.app`, 'Contents', 'Frameworks', `${opts.name} Helper.app`)
  16. }
  17. return opts.name + (opts.platform === 'win32' ? '.exe' : '')
  18. }
  19. test('setting the quiet option does not print messages', (t) => {
  20. const errorLog = console.error
  21. const warningLog = console.warn
  22. let output = ''
  23. console.error = (message) => { output += message }
  24. console.warn = (message) => { output += message }
  25. common.warning('warning', true)
  26. t.is('', output, 'quieted common.warning should not call console.warn')
  27. common.info('info', true)
  28. t.is('', output, 'quieted common.info should not call console.error')
  29. console.error = errorLog
  30. console.warn = warningLog
  31. })
  32. test('download argument test: download.{arch,platform,version} does not overwrite {arch,platform,version}', t => {
  33. const opts = {
  34. download: {
  35. arch: 'ia32',
  36. platform: 'win32',
  37. version: '0.30.0'
  38. },
  39. electronVersion: '0.36.0'
  40. }
  41. const downloadOpts = download.createDownloadOpts(opts, 'linux', 'x64')
  42. t.deepEqual(downloadOpts, {arch: 'x64', platform: 'linux', version: '0.36.0'})
  43. })
  44. test('sanitize app name for use in file/directory names', t => {
  45. t.is('@username-package', common.sanitizeAppName('@username/package'), 'slash should be replaced')
  46. })
  47. test('sanitize app name for use in the out directory name', t => {
  48. let opts = {
  49. arch: 'x64',
  50. name: '@username/package-name',
  51. platform: 'linux'
  52. }
  53. t.is('@username-package-name-linux-x64', common.generateFinalBasename(opts), 'generateFinalBasename output should be sanitized')
  54. })
  55. test('cannot build apps where the name ends in " Helper"', (t) => {
  56. const opts = {
  57. arch: 'x64',
  58. dir: path.join(__dirname, 'fixtures', 'el-0374'),
  59. name: 'Bad Helper',
  60. platform: 'linux'
  61. }
  62. return packager(opts)
  63. .then(
  64. () => { throw new Error('should not finish') },
  65. (err) => t.is(err.message, 'Application names cannot end in " Helper" due to limitations on macOS')
  66. )
  67. })
  68. test('deprecatedParameter moves value in deprecated param to new param if new param is not set', (t) => {
  69. let opts = {
  70. old: 'value'
  71. }
  72. common.deprecatedParameter(opts, 'old', 'new', 'new-value')
  73. t.false(opts.hasOwnProperty('old'), 'old property is not set')
  74. t.true(opts.hasOwnProperty('new'), 'new property is set')
  75. opts.not_overwritten_old = 'another'
  76. common.deprecatedParameter(opts, 'not_overwritten_old', 'new', 'new-value')
  77. t.false(opts.hasOwnProperty('not_overwritten_old'), 'not_overwritten_old property is not set')
  78. t.true(opts.hasOwnProperty('new'), 'new property is set')
  79. t.is('value', opts.new, 'new property is not overwritten')
  80. })
  81. util.testSinglePlatform('defaults test', (t, opts) => {
  82. opts.name = 'defaultsTest'
  83. opts.dir = util.fixtureSubdir('basic')
  84. delete opts.platform
  85. delete opts.arch
  86. let defaultOpts = {
  87. arch: hostArch(),
  88. name: opts.name,
  89. platform: process.platform
  90. }
  91. let finalPath
  92. let resourcesPath
  93. return packager(opts)
  94. .then(paths => {
  95. t.true(Array.isArray(paths), 'packager call should resolve to an array')
  96. t.is(paths.length, 1, 'Single-target run should resolve to a 1-item array')
  97. finalPath = paths[0]
  98. t.is(finalPath, path.join(t.context.workDir, common.generateFinalBasename(defaultOpts)),
  99. 'Path should follow the expected format and be in the cwd')
  100. return fs.stat(finalPath)
  101. }).then(stats => {
  102. t.true(stats.isDirectory(), 'The expected output directory should exist')
  103. resourcesPath = path.join(finalPath, util.generateResourcesPath(defaultOpts))
  104. return fs.stat(path.join(finalPath, generateNamePath(defaultOpts)))
  105. }).then(stats => {
  106. if (common.isPlatformMac(defaultOpts.platform)) {
  107. t.true(stats.isDirectory(), 'The Helper.app should reflect opts.name')
  108. } else {
  109. t.true(stats.isFile(), 'The executable should reflect opts.name')
  110. }
  111. return fs.stat(resourcesPath)
  112. }).then(stats => {
  113. t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory')
  114. return fs.pathExists(path.join(resourcesPath, 'app', 'node_modules', 'run-waterfall'))
  115. }).then(exists => {
  116. t.false(exists, 'The output directory should NOT contain devDependencies by default (prune=true)')
  117. return util.areFilesEqual(path.join(opts.dir, 'main.js'), path.join(resourcesPath, 'app', 'main.js'))
  118. }).then(equal => {
  119. t.true(equal, 'File under packaged app directory should match source file')
  120. return util.areFilesEqual(path.join(opts.dir, 'ignore', 'this.txt'),
  121. path.join(resourcesPath, 'app', 'ignore', 'this.txt'))
  122. }).then(equal => {
  123. t.true(equal, 'File under subdirectory of packaged app directory should match source file and not be ignored by default')
  124. return fs.pathExists(path.join(resourcesPath, 'default_app'))
  125. }).then(exists => {
  126. t.false(exists, 'The output directory should not contain the Electron default_app directory')
  127. return fs.pathExists(path.join(resourcesPath, 'default_app.asar'))
  128. }).then(exists => t.false(exists, 'The output directory should not contain the Electron default_app.asar file'))
  129. })
  130. util.testSinglePlatform('out test', (t, opts) => {
  131. opts.name = 'outTest'
  132. opts.dir = util.fixtureSubdir('basic')
  133. opts.out = 'dist'
  134. let finalPath
  135. return packager(opts)
  136. .then(paths => {
  137. finalPath = paths[0]
  138. t.is(finalPath, path.join('dist', common.generateFinalBasename(opts)),
  139. 'Path should follow the expected format and be under the folder specified in `out`')
  140. return fs.stat(finalPath)
  141. }).then(stats => {
  142. t.true(stats.isDirectory(), 'The expected output directory should exist')
  143. return fs.stat(path.join(finalPath, util.generateResourcesPath(opts)))
  144. }).then(stats => t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory'))
  145. })
  146. util.testSinglePlatform('overwrite test', (t, opts) => {
  147. opts.name = 'overwriteTest'
  148. opts.dir = util.fixtureSubdir('basic')
  149. let finalPath
  150. let testPath
  151. return packager(opts)
  152. .then(paths => {
  153. finalPath = paths[0]
  154. return fs.stat(finalPath)
  155. }).then(stats => {
  156. t.true(stats.isDirectory(), 'The expected output directory should exist')
  157. // Create a dummy file to detect whether the output directory is replaced in subsequent runs
  158. testPath = path.join(finalPath, 'test.txt')
  159. return fs.writeFile(testPath, 'test')
  160. }).then(() => packager(opts)) // Run again, defaulting to overwrite false
  161. .then(paths => fs.stat(testPath))
  162. .then(stats => {
  163. t.true(stats.isFile(), 'The existing output directory should exist as before (skipped by default)')
  164. // Run a third time, explicitly setting overwrite to true
  165. opts.overwrite = true
  166. return packager(opts)
  167. }).then(paths => fs.pathExists(testPath))
  168. .then(exists => t.false(exists, 'The output directory should be regenerated when overwrite is true'))
  169. })
  170. util.testSinglePlatform('overwrite test sans platform/arch set', (t, opts) => {
  171. delete opts.platfrom
  172. delete opts.arch
  173. opts.dir = util.fixtureSubdir('basic')
  174. opts.overwrite = true
  175. return packager(opts)
  176. .then(paths => fs.pathExists(paths[0]))
  177. .then(exists => {
  178. t.true(exists, 'The output directory exists')
  179. return packager(opts)
  180. }).then(paths => fs.pathExists(paths[0]))
  181. .then(exists => t.true(exists, 'The output directory exists'))
  182. })
  183. util.testSinglePlatform('tmpdir test', (t, opts) => {
  184. opts.name = 'tmpdirTest'
  185. opts.dir = path.join(__dirname, 'fixtures', 'basic')
  186. opts.out = 'dist'
  187. opts.tmpdir = path.join(t.context.workDir, 'tmp')
  188. return packager(opts)
  189. .then(paths => fs.stat(path.join(opts.tmpdir, 'electron-packager')))
  190. .then(stats => t.true(stats.isDirectory(), 'The expected temp directory should exist'))
  191. })
  192. util.testSinglePlatform('disable tmpdir test', (t, opts) => {
  193. opts.name = 'disableTmpdirTest'
  194. opts.dir = util.fixtureSubdir('basic')
  195. opts.out = 'dist'
  196. opts.tmpdir = false
  197. return packager(opts)
  198. .then(paths => fs.stat(paths[0]))
  199. .then(stats => t.true(stats.isDirectory(), 'The expected out directory should exist'))
  200. })
  201. util.testSinglePlatform('deref symlink test', (t, opts) => {
  202. opts.name = 'disableSymlinkDerefTest'
  203. opts.dir = util.fixtureSubdir('basic')
  204. opts.derefSymlinks = false
  205. const src = path.join(opts.dir, 'main.js')
  206. const dest = path.join(opts.dir, 'main-link.js')
  207. return fs.ensureSymlink(src, dest)
  208. .then(() => packager(opts))
  209. .then(paths => {
  210. const destLink = path.join(paths[0], 'resources', 'app', 'main-link.js')
  211. return fs.lstat(destLink)
  212. }).then(stats => {
  213. t.true(stats.isSymbolicLink(), 'The expected file should still be a symlink')
  214. return fs.remove(dest)
  215. })
  216. })
  217. function createExtraResourceStringTest (t, opts, platform) {
  218. const extra1Base = 'data1.txt'
  219. const extra1Path = path.join(__dirname, 'fixtures', extra1Base)
  220. opts.name = 'extraResourceStringTest'
  221. opts.dir = util.fixtureSubdir('basic')
  222. opts.out = 'dist'
  223. opts.platform = platform
  224. opts.extraResource = extra1Path
  225. return util.packageAndEnsureResourcesPath(t, opts)
  226. .then(resourcesPath => util.areFilesEqual(extra1Path, path.join(resourcesPath, extra1Base)))
  227. .then(equal => t.true(equal, 'resource file data1.txt should match'))
  228. }
  229. function createExtraResourceArrayTest (t, opts, platform) {
  230. const extra1Base = 'data1.txt'
  231. const extra1Path = path.join(__dirname, 'fixtures', extra1Base)
  232. const extra2Base = 'extrainfo.plist'
  233. const extra2Path = path.join(__dirname, 'fixtures', extra2Base)
  234. opts.name = 'extraResourceArrayTest'
  235. opts.dir = util.fixtureSubdir('basic')
  236. opts.out = 'dist'
  237. opts.platform = platform
  238. opts.extraResource = [extra1Path, extra2Path]
  239. let extra1DistPath
  240. let extra2DistPath
  241. return util.packageAndEnsureResourcesPath(t, opts)
  242. .then(resourcesPath => {
  243. extra1DistPath = path.join(resourcesPath, extra1Base)
  244. extra2DistPath = path.join(resourcesPath, extra2Base)
  245. return fs.pathExists(extra1DistPath)
  246. }).then(exists => {
  247. t.true(exists, 'resource file data1.txt exists')
  248. return util.areFilesEqual(extra1Path, extra1DistPath)
  249. }).then(equal => {
  250. t.true(equal, 'resource file data1.txt should match')
  251. return fs.pathExists(extra2DistPath)
  252. }).then(exists => {
  253. t.true(exists, 'resource file extrainfo.plist exists')
  254. return util.areFilesEqual(extra2Path, extra2DistPath)
  255. }).then(equal => t.true(equal, 'resource file extrainfo.plist should match'))
  256. }
  257. for (const platform of ['darwin', 'linux']) {
  258. util.testSinglePlatform(`extraResource test: string (${platform})`, createExtraResourceStringTest, platform)
  259. util.testSinglePlatform(`extraResource test: array (${platform})`, createExtraResourceArrayTest, platform)
  260. }
  261. util.testSinglePlatform('building for Linux target sanitizes binary name', (t, opts) => {
  262. opts.name = '@username/package-name'
  263. opts.dir = util.fixtureSubdir('basic')
  264. return packager(opts)
  265. .then(paths => {
  266. t.is(1, paths.length, '1 bundle created')
  267. return fs.stat(path.join(paths[0], '@username-package-name'))
  268. }).then(stats => t.true(stats.isFile(), 'The sanitized binary filename should exist'))
  269. })
  270. util.testSinglePlatform('executableName honored when building for Linux target', (t, opts) => {
  271. opts.name = 'PackageName'
  272. opts.executableName = 'my-package'
  273. opts.dir = util.fixtureSubdir('basic')
  274. return packager(opts)
  275. .then(paths => {
  276. t.is(1, paths.length, '1 bundle created')
  277. return fs.stat(path.join(paths[0], 'my-package'))
  278. }).then(stats => t.true(stats.isFile(), 'The executableName-based filename should exist'))
  279. })
  280. util.packagerTest('fails with invalid version', util.invalidOptionTest({
  281. name: 'invalidElectronTest',
  282. dir: util.fixtureSubdir('el-0374'),
  283. electronVersion: '0.0.1',
  284. arch: 'x64',
  285. platform: 'linux',
  286. download: {
  287. quiet: !!process.env.CI
  288. }
  289. }))
  290. util.testSinglePlatform('dir argument test: should work with relative path', (t, opts) => {
  291. opts.name = 'ElectronTest'
  292. opts.dir = path.join('..', 'fixtures', 'el-0374')
  293. opts.electronVersion = '0.37.4'
  294. return packager(opts)
  295. .then(paths => t.is(path.join(t.context.workDir, 'ElectronTest-linux-x64'), paths[0], 'paths returned'))
  296. })