util.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /**
  2. * @module util
  3. */
  4. 'use strict'
  5. const child = require('child_process')
  6. const fs = require('fs')
  7. const path = require('path')
  8. const Promise = require('bluebird')
  9. const debug = require('debug')
  10. /**
  11. * This callback is used across signing and flattening.
  12. * @callback RequestCallback
  13. * @param {?Error} err
  14. */
  15. /** @function */
  16. const debuglog = module.exports.debuglog = debug('electron-osx-sign')
  17. debuglog.log = console.log.bind(console)
  18. /** @function */
  19. const debugwarn = module.exports.debugwarn = debug('electron-osx-sign:warn')
  20. debugwarn.log = console.warn.bind(console)
  21. /** @function */
  22. const isBinaryFileAsync = module.exports.isBinaryFileAsync = Promise.promisify(require('isbinaryfile'))
  23. /** @function */
  24. const removePassword = function (input) {
  25. return input.replace(/(-P |pass:|\/p|-pass )([^ ]+)/, function (match, p1, p2) {
  26. return `${p1}***`
  27. })
  28. }
  29. /** @function */
  30. module.exports.execFileAsync = function (file, args, options) {
  31. if (debuglog.enabled) {
  32. debuglog('Executing...', file, args && Array.isArray(args) ? removePassword(args.join(' ')) : '')
  33. }
  34. return new Promise(function (resolve, reject) {
  35. child.execFile(file, args, options, function (err, stdout, stderr) {
  36. if (err) {
  37. debuglog('Error executing file:', '\n',
  38. '> Stdout:', stdout, '\n',
  39. '> Stderr:', stderr)
  40. reject(err)
  41. return
  42. }
  43. resolve(stdout)
  44. })
  45. })
  46. }
  47. /** @function */
  48. const lstatAsync = module.exports.lstatAsync = Promise.promisify(fs.lstat)
  49. /** @function */
  50. const readdirAsync = module.exports.readdirAsync = Promise.promisify(fs.readdir)
  51. /** @function */
  52. module.exports.readFileAsync = Promise.promisify(fs.readFile)
  53. /** @function */
  54. module.exports.writeFileAsync = Promise.promisify(fs.writeFile)
  55. /**
  56. * This function returns a flattened list of elements from an array of lists.
  57. * @function
  58. * @param {*} list - List.
  59. * @returns Flattened list.
  60. */
  61. var flatList = module.exports.flatList = function (list) {
  62. function populateResult (list) {
  63. if (!Array.isArray(list)) {
  64. result.push(list)
  65. } else if (list.length > 0) {
  66. for (let item of list) if (item) populateResult(item)
  67. }
  68. }
  69. var result = []
  70. populateResult(list)
  71. return result
  72. }
  73. /**
  74. * This function returns the path to app contents.
  75. * @function
  76. * @param {Object} opts - Options.
  77. * @returns {string} App contents path.
  78. */
  79. var getAppContentsPath = module.exports.getAppContentsPath = function (opts) {
  80. return path.join(opts.app, 'Contents')
  81. }
  82. /**
  83. * This function returns the path to app frameworks within contents.
  84. * @function
  85. * @param {Object} opts - Options.
  86. * @returns {string} App frameworks path.
  87. */
  88. var getAppFrameworksPath = module.exports.getAppFrameworksPath = function (opts) {
  89. return path.join(getAppContentsPath(opts), 'Frameworks')
  90. }
  91. /**
  92. * This function returns a promise copying a file from the source to the target.
  93. * @function
  94. * @param {string} source - Source path.
  95. * @param {string} target - Target path.
  96. * @returns {Promise} Promise.
  97. */
  98. module.exports.copyFileAsync = function (source, target) {
  99. debuglog('Copying file...', '\n',
  100. '> Source:', source, '\n',
  101. '> Target:', target)
  102. return new Promise(function (resolve, reject) {
  103. var readStream = fs.createReadStream(source)
  104. readStream.on('error', reject)
  105. var writeStream = fs.createWriteStream(target)
  106. writeStream.on('error', reject)
  107. writeStream.on('close', resolve)
  108. readStream.pipe(writeStream)
  109. })
  110. }
  111. /**
  112. * This function returns a promise with platform resolved.
  113. * @function
  114. * @param {Object} opts - Options.
  115. * @returns {Promise} Promise resolving platform.
  116. */
  117. var detectElectronPlatformAsync = module.exports.detectElectronPlatformAsync = function (opts) {
  118. return new Promise(function (resolve) {
  119. var appFrameworksPath = getAppFrameworksPath(opts)
  120. // The presence of Squirrel.framework identifies a Mac App Store build as used in https://github.com/atom/electron/blob/master/docs/tutorial/mac-app-store-submission-guide.md
  121. return lstatAsync(path.join(appFrameworksPath, 'Squirrel.framework'))
  122. .then(function () {
  123. resolve('darwin')
  124. })
  125. .catch(function () {
  126. resolve('mas')
  127. })
  128. })
  129. }
  130. /**
  131. * This function returns a promise resolving the file path if file binary.
  132. * @function
  133. * @param {string} filePath - Path to file.
  134. * @returns {Promise} Promise resolving file path or undefined.
  135. */
  136. var getFilePathIfBinaryAsync = module.exports.getFilePathIfBinaryAsync = function (filePath) {
  137. return isBinaryFileAsync(filePath)
  138. .then(function (isBinary) {
  139. return isBinary ? filePath : undefined
  140. })
  141. }
  142. /**
  143. * This function returns a promise validating opts.app, the application to be signed or flattened.
  144. * @function
  145. * @param {Object} opts - Options.
  146. * @returns {Promise} Promise.
  147. */
  148. module.exports.validateOptsAppAsync = function (opts) {
  149. if (!opts.app) {
  150. return Promise.reject(new Error('Path to aplication must be specified.'))
  151. }
  152. if (path.extname(opts.app) !== '.app') {
  153. return Promise.reject(new Error('Extension of application must be `.app`.'))
  154. }
  155. return lstatAsync(opts.app)
  156. .thenReturn()
  157. }
  158. /**
  159. * This function returns a promise validating opts.platform, the platform of Electron build. It allows auto-discovery if no opts.platform is specified.
  160. * @function
  161. * @param {Object} opts - Options.
  162. * @returns {Promise} Promise.
  163. */
  164. module.exports.validateOptsPlatformAsync = function (opts) {
  165. if (opts.platform) {
  166. if (opts.platform === 'mas' || opts.platform === 'darwin') {
  167. return Promise.resolve()
  168. } else {
  169. debugwarn('`platform` passed in arguments not supported, checking Electron platform...')
  170. }
  171. } else {
  172. debugwarn('No `platform` passed in arguments, checking Electron platform...')
  173. }
  174. return detectElectronPlatformAsync(opts)
  175. .then(function (platform) {
  176. opts.platform = platform
  177. })
  178. }
  179. /**
  180. * This function returns a promise resolving all child paths within the directory specified.
  181. * @function
  182. * @param {string} dirPath - Path to directory.
  183. * @returns {Promise} Promise resolving child paths needing signing in order.
  184. */
  185. module.exports.walkAsync = function (dirPath) {
  186. debuglog('Walking... ' + dirPath)
  187. var unlinkAsync = Promise.promisify(fs.unlink)
  188. function _walkAsync (dirPath) {
  189. return readdirAsync(dirPath)
  190. .then(function (names) {
  191. return Promise.map(names, function (name) {
  192. var filePath = path.join(dirPath, name)
  193. return lstatAsync(filePath)
  194. .then(function (stat) {
  195. if (stat.isFile()) {
  196. switch (path.extname(filePath)) {
  197. case '': // Binary
  198. if (path.basename(filePath)[0] !== '.') {
  199. return getFilePathIfBinaryAsync(filePath)
  200. } // Else reject hidden file
  201. break
  202. case '.dylib': // Dynamic library
  203. case '.node': // Native node addon
  204. return filePath
  205. case '.cstemp': // Temporary file generated from past codesign
  206. debuglog('Removing... ' + filePath)
  207. return unlinkAsync(filePath)
  208. .thenReturn(undefined)
  209. default:
  210. if (path.extname(filePath).indexOf(' ') >= 0) {
  211. // Still consider the file as binary if extension seems invalid
  212. return getFilePathIfBinaryAsync(filePath)
  213. }
  214. }
  215. } else if (stat.isDirectory() && !stat.isSymbolicLink()) {
  216. return _walkAsync(filePath)
  217. .then(function (result) {
  218. switch (path.extname(filePath)) {
  219. case '.app': // Application
  220. case '.framework': // Framework
  221. result.push(filePath)
  222. }
  223. return result
  224. })
  225. }
  226. })
  227. })
  228. })
  229. }
  230. return _walkAsync(dirPath)
  231. .then(flatList)
  232. }