diff --git a/.gitignore b/.gitignore index 16ecd65e1..0a7848506 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,13 @@ coverage/ shell/bin/linux shell/bin/darwin shell/bin/win32 + +shell/scripts/bin +shell/scripts/build + +dist/darwin +dist/linux +dist/windows +dist/*.dmg +dist/*.tar.gz +dist/*.exe diff --git a/README.md b/README.md index 1ec1f5809..b7ddb314a 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,19 @@ Once this script has completed, you can launch the shell-based Copay by running: npm run shell ``` +## Building Native Shell Binaries/Installers (OSX) + +``` +npm run dist +``` + +This script will download atom shell binaries and combine them with Copay sources +to build a DMG for osx-x64, an installer EXE for win32, and a .tar.gz for linux-x64. +It was developed to be run on OSX. The outputs are copied to the dist directory. + +DMG is created with hdiutil +EXE is created with makensis (brew install makensis) + ## Tests Open test/index.html in your browser to test the models. Install and run karma diff --git a/dist/README.md b/dist/README.md new file mode 100644 index 000000000..1d883f95d --- /dev/null +++ b/dist/README.md @@ -0,0 +1 @@ +This is the destination directory for the built atom-shell binaries and the files used to create them \ No newline at end of file diff --git a/package.json b/package.json index 3bbbc4c45..1e943a55b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "shell": "node shell/scripts/launch.js", "setup-shell": "node shell/scripts/download-atom-shell.js", "chrome": "source browser-extensions/chrome/build.sh", - "firefox": "source browser-extensions/firefox/build.sh" + "firefox": "source browser-extensions/firefox/build.sh", + "dist": "node shell/scripts/dist.js" }, "homepage": "https://github.com/bitpay/copay", "devDependencies": { diff --git a/shell/README.md b/shell/README.md index 970f9882d..36c1408e4 100644 --- a/shell/README.md +++ b/shell/README.md @@ -8,4 +8,5 @@ using [Atom Shell](https://github.com/atom/atom-shell). ## Building -Automated build scripts are currently being developed. +Run from the top level (copay) directory: +npm run dist diff --git a/shell/index.js b/shell/index.js index 4f9f5abf8..71e4543c9 100644 --- a/shell/index.js +++ b/shell/index.js @@ -12,7 +12,7 @@ module.exports = function(copay) { // quit when all windows are closed app.on('window-all-closed', function() { - if (process.platform !== 'darwin') app.quit(); + app.quit(); }); // initilization when ready diff --git a/shell/scripts/dist.js b/shell/scripts/dist.js new file mode 100644 index 000000000..1555dbe70 --- /dev/null +++ b/shell/scripts/dist.js @@ -0,0 +1,181 @@ +require('shelljs/global'); +var color = require('cli-color'); + +var download = require('./lib/download')(); +var async = require('async'); + +var atom_version = 'v0.13.0'; + +var app_root = './'; + +var build_dir = 'shell/scripts/build'; +var dist_dir = 'dist'; + +var darwin_app_dir = '/Copay.app/Contents/Resources/app'; +var linux_app_dir = '/resources/app'; +var windows_app_dir = '/resources/app'; + +console.log(color.blue('{copay}'), ''); +console.log(color.blue('{copay}'), 'Preparing to build Copay binaries'); +console.log(color.blue('{copay}'), ''); + +/* Clean up before the build */ +rm('-rf', build_dir); +rm(dist_dir + '/Copay*'); +rm('-rf', dist_dir + '/darwin', dist_dir + '/linux', dist_dir + '/windows'); + +// Download the atom shell binaries. If you exceed your download quota, +// just download the zips manually and unpack them into shell/scripts/bin/ +async.series([ + function(callback) { + download.atom(atom_version, 'darwin', 'x64', callback); + }, + function(callback){ + download.atom(atom_version, 'linux', 'x64', callback); + }, + function(callback) { + download.atom(atom_version, 'win32', 'ia32', callback); + }, + function() { + runBuild(); + }]); + +function runBuild() { + + mkdir(build_dir); + + /* DARWIN BUILD */ + + console.log(color.blue('{copay}'), ''); + console.log(color.blue('{copay}'), 'Starting DARWIN build'); + console.log(color.blue('{copay}'), ''); + + // Copy the core atom shell + cp('-r', app_root + '/shell/scripts/bin/darwin/*', build_dir); + mv(build_dir + '/Atom.app', build_dir + '/Copay.app'); + mv(build_dir + '/Copay.app/Contents/MacOS/Atom', build_dir + '/Copay.app/Contents/MacOS/Copay'); + + // Replace Atom darwin assets with Copay assets + cp(app_root + '/shell/assets/darwin/copay.icns', build_dir + '/Copay.app/Contents/Resources/copay.icns'); + cp('-f', app_root + '/shell/assets/darwin/Info.plist', build_dir + '/Copay.app/Contents/Info.plist'); + rm(build_dir + '/Copay.app/Contents/Resources/atom.icns'); + + // Copy Copay sources + cp('-r', app_root + '/css', build_dir + darwin_app_dir); + cp('-r', app_root + '/js', build_dir + darwin_app_dir); + cp('-r', app_root + '/font', build_dir + darwin_app_dir); + cp('-r', app_root + '/img', build_dir + darwin_app_dir); + cp('-r', app_root + '/lib', build_dir + darwin_app_dir); + cp('-r', app_root + '/sound', build_dir + darwin_app_dir); + cp('-r', app_root + '/shell/*.js*', build_dir + darwin_app_dir + '/shell'); + cp('-r', app_root + '/shell/lib/*', build_dir + darwin_app_dir + '/shell/lib'); + cp(app_root + '*.js', build_dir + darwin_app_dir); + cp(app_root + '*.json', build_dir + darwin_app_dir); + cp(app_root + '*.html', build_dir + darwin_app_dir); + + // Copay needs express, put other node deps here if you need any + cp('-r', app_root + '/node_modules/express', build_dir + darwin_app_dir + "/node_modules"); + + // Clean up extra Atom sources + rm('-r', build_dir + darwin_app_dir + '/../*.lproj'); + rm('-r', build_dir + darwin_app_dir + '/../default_app'); + + mkdir('-p', app_root + '/dist/darwin'); + cp('-r', app_root + build_dir + '/*', app_root + '/dist/darwin/'); + rm('-rf', app_root + build_dir + '/*'); + + console.log(color.blue('{copay}'), 'Copied files to ' + 'dist/darwin'); + + if (which('hdiutil') != null) { + cd(app_root + '/dist/darwin'); + exec('ln -s /Applications Applications'); + exec('hdiutil create ../Copay-darwin-x64.dmg -volname "Copay Installer - Drag Copay to Applications Folder" -fs HFS+ -srcfolder "."'); + exec("rm Applications"); + cd('../..'); + } + + /* Linux Build */ + + console.log(color.blue('{copay}'), ''); + console.log(color.blue('{copay}'), 'Starting LINUX build'); + console.log(color.blue('{copay}'), ''); + + // Copy the core atom shell + cp('-r', app_root + '/shell/scripts/bin/linux/*', build_dir); + mv(build_dir + '/atom', build_dir + '/Copay'); + + // Copy Copay sources + cp('-r', app_root + '/css', build_dir + linux_app_dir); + cp('-r', app_root + '/js', build_dir + linux_app_dir); + cp('-r', app_root + '/font', build_dir + linux_app_dir); + cp('-r', app_root + '/img', build_dir + linux_app_dir); + cp('-r', app_root + '/lib', build_dir + linux_app_dir); + cp('-r', app_root + '/sound', build_dir + linux_app_dir); + cp('-r', app_root + '/shell/*.js*', build_dir + linux_app_dir + '/shell'); + cp('-r', app_root + '/shell/lib/*', build_dir + linux_app_dir + '/shell/lib'); + cp(app_root + '*.js', build_dir + linux_app_dir); + cp(app_root + '*.json', build_dir + linux_app_dir); + cp(app_root + '*.html', build_dir + linux_app_dir); + + cp('-r', app_root + '/node_modules/express', build_dir + linux_app_dir + "/node_modules"); + + // Clean up extra Atom sources + rm('-r', build_dir + linux_app_dir + '/../default_app'); + cp('-r', app_root + build_dir + '/*', app_root + '/dist/linux'); + rm('-rf', app_root + build_dir + '/*'); + + exec('tar czf ./dist/Copay-linux-x64.tar.gz -C ./dist/linux .'); + + console.log(color.blue('{copay}'), 'Copied files to ' + 'dist/linux'); + + /* Windows Build */ + + console.log(color.blue('{copay}'), ''); + console.log(color.blue('{copay}'), 'Starting WIN32 build'); + console.log(color.blue('{copay}'), ''); + + // Copy the core atom shell + cp('-r', app_root + '/shell/scripts/bin/win32/*', build_dir); + mv(build_dir + '/atom.exe', build_dir + '/Copay.exe'); + + // Copy Copay sources + cp('-r', app_root + '/css', build_dir + windows_app_dir); + cp('-r', app_root + '/js', build_dir + windows_app_dir); + cp('-r', app_root + '/font', build_dir + windows_app_dir); + cp('-r', app_root + '/img', build_dir + windows_app_dir); + cp('-r', app_root + '/lib', build_dir + windows_app_dir); + cp('-r', app_root + '/sound', build_dir + windows_app_dir); + cp('-r', app_root + '/shell/*.js*', build_dir + windows_app_dir + '/shell'); + cp('-r', app_root + '/shell/lib/*', build_dir + windows_app_dir + '/shell/lib'); + cp(app_root + '*.js', build_dir + windows_app_dir); + cp(app_root + '*.json', build_dir + windows_app_dir); + cp(app_root + '*.html', build_dir + windows_app_dir); + + cp('-r', app_root + '/node_modules/express', build_dir + windows_app_dir + "/node_modules"); + + cp(app_root + "/shell/assets/win32/*", build_dir); + + rm('-r', build_dir + windows_app_dir + '/../default_app'); + + mkdir('-p', app_root + '/dist/windows'); + cp('-r', app_root + build_dir + '/*', app_root + '/dist/windows/'); + + rm('-rf', app_root + build_dir + '/*'); + + console.log(color.blue('{copay}'), 'Copied files to ' + 'dist/windows'); + + + // generating windows installer requires makensis + // install on OSX with "brew install makensis" + if (which('makensis') != null) { + console.log(color.blue('{copay}'), 'Running NSIS to generate win32 installer'); + cd('dist/windows'); + exec('makensis -V2 build-installer.nsi'); + cd("../../"); + cp('dist/windows/copay-setup.exe', app_root + '/dist/Copay-setup-win32.exe') + } + + console.log(color.blue('{copay}')); + console.log(color.blue('{copay}'), 'BUILD COMPLETE'); + console.log(color.blue('{copay}'), 'Files can be found in the dist directory'); +} diff --git a/shell/scripts/lib/download.js b/shell/scripts/lib/download.js new file mode 100644 index 000000000..d13f692a6 --- /dev/null +++ b/shell/scripts/lib/download.js @@ -0,0 +1,104 @@ +var path = require('path'); +var fs = require('fs'); +var GitHub = require('github-releases'); +var async = require('async'); +var readl = require('readline'); +var color = require('cli-color'); +var github = new GitHub({ repo: 'atom/atom-shell' }); +var exec = require('child_process').exec; +var os = require('os'); + +var Static = function() { + + return { + atom: function(version, platform, arch, callback) { + var targetRoot = path.normalize(__dirname + '/../bin/'), + target = path.normalize(__dirname + '/../bin/' + platform); + + console.log(color.blue('{copay}'), 'Downloading atom-shell ' + version + ' ' + platform + '-' + arch); + + if(!fs.existsSync(targetRoot)) { + fs.mkdirSync(targetRoot); + } + + if (!fs.existsSync(target)) { + fs.mkdirSync(target); + } else { + console.log(color.blue('{copay}'), platform + '-' + arch + ' ' + ' already downloaded'); + callback.call(this, null); + return; + } + + + + console.log(color.blue('{copay}'), 'getting atom-shell release ' + version); + + github.getReleases({ tag_name: version }, function(err, releases) { + var filename = 'atom-shell-' + version + '-' + platform + '-' + arch + '.zip'; + + if (err || !releases.length) { + console.log(err); + return console.log(color.blue('{copay}'), 'Release not found'); + } + + console.log(color.blue('{copay}'), 'looking for prebuilt binary ' + filename); + + for (var a = 0; a < releases[0].assets.length; a++) { + var asset = releases[0].assets[a]; + + if (asset.name === filename) { + + console.log(color.blue('{copay}'), 'downloading ' + asset.name); + + var rl = readl.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.write(' bytes received: 0'); + + return github.downloadAsset(asset, function(err, inStream) { + if (err) { + console.log(err); + process.exit(); + } + + var bytes = 0; + + inStream.on('data', function(chunk) { + rl.write(null, { ctrl: true, name: 'u' }); + rl.write(' bytes received: ' + (bytes + chunk.length)); + bytes += chunk.length; + }); + + inStream.on('end', function() { + rl.close(); + console.log(''); + console.log(color.blue('{copay}'), 'downloaded!'); + }); + + var out = target; + var tmp = os.tmpDir() + '/atom-shell.zip'; + var outStream = fs.createWriteStream(tmp); + + outStream.on('finish', function() { + console.log(color.blue('{copay}'), 'unzipping archive'); + exec('unzip -o ' + tmp + ' -d ' + out, function(err, stdout, stderr) { + console.log(err || stderr || (color.blue('{copay}') + ' done!')) + callback.call(this, null); + }); + }); + + inStream.pipe(outStream); + + }); + } + } + + }); + + } + } +} + +module.exports = Static;