├── .gitignore ├── LICENSE.md ├── README.md ├── package.json └── tasks └── download-atom-shell-task.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.swp 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-download-electron 2 | 3 | Grunt tasks for downloading [Electron](https://github.com/electron/electron) and the 4 | compatible version of `chromedriver`. 5 | 6 | ## Installation 7 | 8 | Install npm package, next to your project's `Gruntfile.js` file: 9 | 10 | ```sh 11 | npm install --save-dev grunt-download-electron 12 | ``` 13 | 14 | Add this line to your project's `Gruntfile.js`: 15 | 16 | ```js 17 | grunt.loadNpmTasks('grunt-download-electron'); 18 | ``` 19 | 20 | ## Options 21 | 22 | * `version` - **Required** The version of Electron you want to download. 23 | * `outputDir` - **Required** Where to put the downloaded Electron release. 24 | * `downloadDir` - Where to find and save cached downloaded Electron releases. 25 | * `symbols` - Download debugging symbols instead of binaries, default to `false`. 26 | * `rebuild` - Whether to rebuild native modules after Electron is downloaded. 27 | * `apm` - The path to apm. 28 | * `token` - The [OAuth token](https://developer.github.com/v3/oauth/) to use for GitHub API requests. 29 | * `appDir` - Where to find the app when rebuilding the native modules. Defaults to the current directory. 30 | 31 | ### Usage 32 | 33 | Add the necessary configuration to your `Gruntfile.js`: 34 | 35 | ```js 36 | module.exports = function(grunt) { 37 | grunt.initConfig({ 38 | 'download-electron': { 39 | version: '0.24.0', 40 | outputDir: 'electron' 41 | } 42 | }); 43 | }; 44 | ``` 45 | 46 | or your `Gruntfile.coffee`: 47 | 48 | ```coffee 49 | module.exports = (grunt) -> 50 | grunt.initConfig 51 | 'download-electron': 52 | version: '0.24.0' 53 | outputDir: 'electron' 54 | ``` 55 | 56 | Then you can download Electron to the path you specified: 57 | 58 | ```shell 59 | $ grunt download-electron 60 | ``` 61 | 62 | If you're doing selenium-testing of your Electron app, you'll need 63 | `chromedriver`, which is distributed with Electron. To download it into the 64 | `electron` directory: 65 | 66 | ```shell 67 | $ grunt download-electron-chromedriver 68 | ``` 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-download-electron", 3 | "description": "Grunt task to download electron", 4 | "version": "2.1.4", 5 | "main": "grunt.js", 6 | "dependencies": { 7 | "decompress-zip": "0.0.4", 8 | "grunt": "0.4", 9 | "wrench": "1.5.4", 10 | "github-releases": "0.3.2", 11 | "progress": "1.1.2" 12 | }, 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "http://github.com/electron/grunt-download-electron/raw/master/LICENSE.md" 17 | } 18 | ], 19 | "homepage": "https://github.com/electron/grunt-download-electron", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/electron/grunt-download-electron.git" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/electron/grunt-download-electron/issues" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tasks/download-atom-shell-task.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | os = require 'os' 4 | wrench = require 'wrench' 5 | GitHub = require 'github-releases' 6 | Progress = require 'progress' 7 | 8 | TaskName = "download-electron" 9 | 10 | module.exports = (grunt) -> 11 | spawn = (options, callback) -> 12 | childProcess = require 'child_process' 13 | stdout = [] 14 | stderr = [] 15 | error = null 16 | proc = childProcess.spawn options.cmd, options.args, options.opts 17 | proc.stdout.on 'data', (data) -> stdout.push data.toString() 18 | proc.stderr.on 'data', (data) -> stderr.push data.toString() 19 | proc.on 'error', (processError) -> error ?= processError 20 | proc.on 'exit', (code, signal) -> 21 | error ?= new Error(signal) if code != 0 22 | results = stderr: stderr.join(''), stdout: stdout.join(''), code: code 23 | grunt.log.error results.stderr if code != 0 24 | callback error, results, code 25 | 26 | getArch = -> 27 | switch process.platform 28 | when 'win32' then 'ia32' 29 | when 'darwin' then 'x64' 30 | else process.arch 31 | 32 | getApmPath = -> 33 | apmPath = path.join 'apm', 'node_modules', 'atom-package-manager', 'bin', 'apm' 34 | apmPath = 'apm' unless grunt.file.isFile apmPath 35 | 36 | if process.platform is 'win32' then "#{apmPath}.cmd" else apmPath 37 | 38 | getAtomShellVersion = (directory) -> 39 | versionPath = path.join directory, 'version' 40 | if grunt.file.isFile versionPath 41 | grunt.file.read(versionPath).trim() 42 | else 43 | null 44 | 45 | copyDirectory = (fromPath, toPath) -> 46 | wrench.copyDirSyncRecursive fromPath, toPath, 47 | forceDelete: true 48 | excludeHiddenUnix: false 49 | inflateSymlinks: false 50 | 51 | unzipFile = (zipPath, callback) -> 52 | grunt.verbose.writeln "Unzipping #{path.basename(zipPath)}." 53 | directoryPath = path.dirname zipPath 54 | 55 | if process.platform is 'darwin' 56 | # The zip archive of darwin build contains symbol links, only the "unzip" 57 | # command can handle it correctly. 58 | spawn {cmd: 'unzip', args: [zipPath, '-d', directoryPath]}, (error) -> 59 | fs.unlinkSync zipPath 60 | callback error 61 | else 62 | DecompressZip = require('decompress-zip') 63 | unzipper = new DecompressZip(zipPath) 64 | unzipper.on 'error', callback 65 | unzipper.on 'extract', -> 66 | fs.closeSync unzipper.fd 67 | fs.unlinkSync zipPath 68 | 69 | # Make sure atom/electron is executable on Linux 70 | if process.platform is 'linux' 71 | electronAppPath = path.join(directoryPath, 'electron') 72 | fs.chmodSync(electronAppPath, '755') if fs.existsSync(electronAppPath) 73 | 74 | atomAppPath = path.join(directoryPath, 'atom') 75 | fs.chmodSync(atomAppPath, '755') if fs.existsSync(atomAppPath) 76 | 77 | callback null 78 | unzipper.extract(path: directoryPath) 79 | 80 | downloadAndUnzip = (inputStream, zipFilePath, callback) -> 81 | wrench.mkdirSyncRecursive(path.dirname(zipFilePath)) 82 | 83 | unless process.platform is 'win32' 84 | len = parseInt(inputStream.headers['content-length'], 10) 85 | progress = new Progress('downloading [:bar] :percent :etas', {complete: '=', incomplete: ' ', width: 20, total: len}) 86 | 87 | outputStream = fs.createWriteStream(zipFilePath) 88 | inputStream.pipe outputStream 89 | inputStream.on 'error', callback 90 | outputStream.on 'error', callback 91 | outputStream.on 'close', unzipFile.bind this, zipFilePath, callback 92 | inputStream.on 'data', (chunk) -> 93 | return if process.platform is 'win32' 94 | 95 | process.stdout.clearLine?() 96 | process.stdout.cursorTo?(0) 97 | progress.tick(chunk.length) 98 | 99 | rebuildNativeModules = (apm, previousVersion, currentVersion, needToRebuild, callback, appDir) -> 100 | if currentVersion isnt previousVersion and needToRebuild 101 | grunt.verbose.writeln "Rebuilding native modules for new electron version #{currentVersion}." 102 | apm ?= getApmPath() 103 | 104 | # When we spawn apm, we still want to use the global environment variables 105 | options = env: {} 106 | options.env[key] = value for key, value of process.env 107 | options.env.ATOM_NODE_VERSION = currentVersion.substr(1) 108 | 109 | # If the appDir has been set, then that is where we want to perform the rebuild. 110 | # it defaults to the current directory 111 | options.cwd = appDir if appDir 112 | spawn {cmd: apm, args: ['rebuild'], opts: options}, callback 113 | else 114 | callback() 115 | 116 | grunt.registerTask TaskName, 'Download electron', -> 117 | @requiresConfig "#{TaskName}.version", "#{TaskName}.outputDir" 118 | {version, outputDir, downloadDir, symbols, rebuild, apm, token, appDir} = grunt.config TaskName 119 | downloadDir ?= path.join os.tmpdir(), 'downloaded-electron' 120 | symbols ?= false 121 | rebuild ?= true 122 | apm ?= getApmPath() 123 | version = "v#{version}" 124 | versionDownloadDir = path.join(downloadDir, version) 125 | appDir ?= process.cwd() 126 | 127 | done = @async() 128 | 129 | # Do nothing if the desired version of electron is already installed. 130 | currentAtomShellVersion = getAtomShellVersion(outputDir) 131 | return done() if currentAtomShellVersion is version 132 | 133 | # Install a cached download of electron if one is available. 134 | if getAtomShellVersion(versionDownloadDir)? 135 | grunt.verbose.writeln("Installing cached electron #{version}.") 136 | copyDirectory(versionDownloadDir, outputDir) 137 | rebuildNativeModules apm, currentAtomShellVersion, version, rebuild, done, appDir 138 | return 139 | 140 | # Request the assets. 141 | github = new GitHub({repo: 'electron/electron', token}) 142 | github.getReleases tag_name: version, (error, releases) -> 143 | unless releases?.length > 0 144 | grunt.log.error "Cannot find electron #{version} from GitHub", error 145 | return done false 146 | 147 | 148 | atomShellAssets = releases[0].assets.filter ({name}) -> name.indexOf('atom-shell-') is 0 149 | if atomShellAssets.length > 0 150 | projectName = 'atom-shell' 151 | else 152 | projectName = 'electron' 153 | 154 | # Which file to download 155 | filename = 156 | if symbols 157 | "#{projectName}-#{version}-#{process.platform}-#{getArch()}-symbols.zip" 158 | else 159 | "#{projectName}-#{version}-#{process.platform}-#{getArch()}.zip" 160 | 161 | # Find the asset of current platform. 162 | for asset in releases[0].assets when asset.name is filename 163 | github.downloadAsset asset, (error, inputStream) -> 164 | if error? 165 | grunt.log.error "Cannot download electron #{version}", error 166 | return done false 167 | 168 | # Save file to cache. 169 | grunt.verbose.writeln "Downloading electron #{version}." 170 | downloadAndUnzip inputStream, path.join(versionDownloadDir, "#{projectName}.zip"), (error) -> 171 | if error? 172 | grunt.log.error "Failed to download electron #{version}", error 173 | return done false 174 | 175 | grunt.verbose.writeln "Installing electron #{version}." 176 | copyDirectory(versionDownloadDir, outputDir) 177 | rebuildNativeModules apm, currentAtomShellVersion, version, rebuild, done, appDir 178 | return 179 | 180 | grunt.log.error "Cannot find #{filename} in electron #{version} release" 181 | done false 182 | 183 | grunt.registerTask "#{TaskName}-chromedriver", 'Download the chromedriver binary distributed with electron', -> 184 | @requiresConfig "#{TaskName}.version", "#{TaskName}.outputDir" 185 | {version, outputDir, downloadDir, token} = grunt.config(TaskName) 186 | version = "v#{version}" 187 | downloadDir ?= path.join os.tmpdir(), 'downloaded-electron' 188 | chromedriverPath = path.join(outputDir, "chromedriver") 189 | 190 | done = @async() 191 | 192 | # Chromedriver is only distributed with the first patch release for any 193 | # given major and minor version of electron. 194 | versionWithChromedriver = version.split(".").slice(0, 2).join(".") + ".0" 195 | downloadPath = path.join(downloadDir, "#{versionWithChromedriver}-chromedriver") 196 | 197 | # Do nothing if the desired version of electron is already installed with 198 | # a chromedriver executable. 199 | currentAtomShellVersion = getAtomShellVersion(outputDir) 200 | return done() if currentAtomShellVersion is version and grunt.file.isDir(chromedriverPath) 201 | 202 | # Use a cached download of chromedriver if one exists. 203 | if grunt.file.isDir(downloadPath) 204 | grunt.verbose.writeln("Installing cached chromedriver #{versionWithChromedriver}.") 205 | copyDirectory(downloadPath, chromedriverPath) 206 | return done() 207 | 208 | # Request the assets. 209 | github = new GitHub({repo: 'atom/electron', token}) 210 | github.getReleases tag_name: versionWithChromedriver, (error, releases) -> 211 | unless releases?.length > 0 212 | grunt.log.error "Cannot find electron #{versionWithChromedriver} from GitHub", error 213 | return done false 214 | 215 | # Find the asset for the current platform and architecture. 216 | assetNameRegex = ///chromedriver-.*-#{process.platform}-#{getArch()}/// 217 | for asset in releases[0].assets when assetNameRegex.test(asset.name) 218 | github.downloadAsset asset, (error, inputStream) -> 219 | if error? 220 | grunt.log.error "Cannot download chromedriver for electron #{versionWithChromedriver}", error 221 | return done false 222 | 223 | # Save file to cache. 224 | grunt.verbose.writeln "Downloading chromedriver for electron #{versionWithChromedriver}." 225 | downloadAndUnzip inputStream, path.join(downloadPath, "chromedriver.zip"), (error) -> 226 | if error? 227 | grunt.log.error "Failed to download chromedriver for electron #{versionWithChromedriver}", error 228 | return done false 229 | 230 | grunt.verbose.writeln "Installing chromedriver for electron #{versionWithChromedriver}." 231 | copyDirectory(downloadPath, chromedriverPath) 232 | 233 | # Make sure chromedriver is executable on Linux 234 | if process.platform is 'linux' 235 | chromedriverExecutablePath = path.join(chromedriverPath, 'chromedriver') 236 | fs.chmodSync(chromedriverExecutablePath, '755') if fs.existsSync(chromedriverExecutablePath) 237 | 238 | done() 239 | return 240 | 241 | grunt.log.error "Cannot find chromedriver in electron #{versionWithChromedriver} release" 242 | done false 243 | --------------------------------------------------------------------------------