From 83b342bfa4910b150b9c01e689e36e7071c151e7 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 30 Sep 2015 15:33:12 -0400 Subject: [PATCH 1/4] :zap: Enable sourcemaps --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f56316c946d..8cb4330f7f4 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "fonts/*" ], "browserify": { + "debug": true, "entries": [ "./src/index.js" ] From 7f7f029052f9b62ff174c76a7a439cd63152ef14 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 30 Sep 2015 15:45:59 -0400 Subject: [PATCH 2/4] :zap: :tada: auto detect if you can sign builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than having to set an environment variable to say you don’t have the cert, let the build tooling figure it out for you and log more useful messages along the way. If you have the certificate, you’ll now see: ![](https://cldup.com/--k6ry1Av7-2000x2000.png) And if you don’t, you’ll no longer see a really confusing stack trace but instead: ![](https://cldup.com/v5gx_zbkUo-3000x3000.png) --- package.json | 2 + tasks/darwin.js | 144 ++++++++++++++++++++++++++++++++++++++---------- tasks/run.js | 108 ++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+), 30 deletions(-) create mode 100644 tasks/run.js diff --git a/package.json b/package.json index 8cb4330f7f4..9c5d68953b8 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "bootstrap": "https://github.com/twbs/bootstrap/archive/v3.3.5.tar.gz", "browserify": "^10.2.4", "bugsnag-js": "^2.4.8", + "chalk": "^1.1.1", "d3": "^3.5.5", "del": "^1.2.0", "domready": "^1.0.8", @@ -102,6 +103,7 @@ "eslint": "^0.24.1", "eslint-config-mongodb-js": "^0.1.4", "event-stream": "^3.3.1", + "figures": "^1.4.0", "font-awesome": "https://github.com/FortAwesome/Font-Awesome/archive/v4.3.0.tar.gz", "glob": "^5.0.14", "gulp": "^3.9.0", diff --git a/tasks/darwin.js b/tasks/darwin.js index acc86a007ed..bb872debbcd 100644 --- a/tasks/darwin.js +++ b/tasks/darwin.js @@ -1,9 +1,14 @@ var path = require('path'); var pkg = require(path.resolve(__dirname, '../package.json')); var fs = require('fs'); -var cp = require('child_process'); +var run = require('./run'); +var format = require('util').format; +var chalk = require('chalk'); +var figures = require('figures'); var series = require('run-series'); var _ = require('lodash'); +var packager = require('electron-packager'); +var createDMG = require('electron-installer-dmg'); var debug = require('debug')('scout:tasks:darwin'); @@ -11,9 +16,6 @@ var NAME = pkg.product_name; var PACKAGE = path.join('dist', NAME + '-darwin-x64'); var APP_PATH = path.join(PACKAGE, NAME + '.app'); -var packager = require('electron-packager'); -var createDMG = require('electron-installer-dmg'); - module.exports.ELECTRON = path.join(APP_PATH, 'Contents', 'MacOS', 'Electron'); module.exports.RESOURCES = path.join(APP_PATH, 'Contents', 'Resources'); @@ -29,7 +31,6 @@ var PACKAGER_CONFIG = { prune: true, 'app-bundle-id': 'com.mongodb.compass', 'app-version': pkg.version, - sign: '90E39AA7832E95369F0FC6DAF823A04DFBD9CF7A', protocols: [ { name: 'MongoDB Prototcol', @@ -38,11 +39,6 @@ var PACKAGER_CONFIG = { ] }; -// Adjust config via environment variables -if (process.env.SCOUT_INSTALLER_UNSIGNED !== undefined) { - PACKAGER_CONFIG.sign = null; -} - // @todo (imlucas): Standardize `electron-installer-dmg` // options w/ `electron-installer-squirrel-windows`. var INSTALLER_CONFIG = { @@ -69,39 +65,127 @@ var INSTALLER_CONFIG = { ] }; -module.exports.build = function(done) { - fs.exists(APP_PATH, function(exists) { - if (exists) { - debug('.app already exists. skipping packager run.'); - return done(); +var CODESIGN_IDENTITY_COMMON_NAME = 'Developer ID Application: Matt Kangas (ZD3CL9MT3L)'; +var CODESIGN_IDENTITY_SHA1 = '90E39AA7832E95369F0FC6DAF823A04DFBD9CF7A'; + +/** + * Checks if the current environment can actually sign builds. + * If signing can be done, `electron-packager`'s config will + * be updated to sign artifacts. If not, gracefully degrade + * + * @param {Function} fn - Callback. + */ +function addCodesignIdentityIfAvailable(fn) { + run('certtool', ['y'], function(err, output) { + if (err) { + debug('Failed to list certificates. Build will not be signed.'); + fn(); + return; } - debug('running packager to create electron binaries...'); - packager(PACKAGER_CONFIG, done); + if (output.indexOf(CODESIGN_IDENTITY_COMMON_NAME) === -1) { + debug('Signing identity `%s` not detected. Build will not be signed.', + CODESIGN_IDENTITY_COMMON_NAME); + fn(); + return; + } + + PACKAGER_CONFIG.sign = CODESIGN_IDENTITY_SHA1; + debug('The signing identity `%s` is available! ' + + 'This build will be signed!', CODESIGN_IDENTITY_COMMON_NAME); + + console.log(chalk.green.bold(figures.tick), + format(' This build will be signed using the `%s` signing identity', + CODESIGN_IDENTITY_COMMON_NAME)); + fn(); }); -}; +} + +module.exports.build = function(done) { + addCodesignIdentityIfAvailable(function(err) { + if (err) return done(err); -var verify = function(done) { - var cmd = 'codesign --verify "' + APP_PATH + '"'; - debug('Verifying `%s` has been signed...', APP_PATH); - cp.exec(cmd, done); + fs.exists(APP_PATH, function(exists) { + if (exists && process.env.NODE_ENV !== 'production') { + debug('.app already exists. skipping packager run.'); + return done(); + } + debug('running electron-packager...'); + packager(PACKAGER_CONFIG, done); + }); + }); }; -module.exports.installer = function(done) { - debug('creating installer...'); +/** + * If the app is supposed to be signed, verify that + * the signing was actually completed correctly. + * If signing is not available, print helpful details + * on working with unsigned builds. + * + * @param {Function} done - Callback which receives `(err)`. + */ +function verify(done) { + if (!PACKAGER_CONFIG.sign) { + console.error(chalk.yellow.bold(figures.warning), + ' User confusion ahead!'); - var tasks = []; - if (PACKAGER_CONFIG.sign) { - tasks.push(verify); + console.error(chalk.gray( + ' The default preferences for OSX Gatekeeper will not', + 'allow users to run unsigned applications.')); + + console.error(chalk.gray( + ' However, we\'re going to continue building', + 'the app and an installer because you\'re most likely')); + + console.error(chalk.gray( + ' a developer trying to test', + 'the app\'s installation process.')); + + console.error(chalk.gray( + ' For more information on OSX Gatekeeper and how to change your', + 'system preferences to run unsigned applications,')); + console.error(chalk.gray(' please see', + 'https://support.apple.com/en-us/HT202491')); + debug('Build is not signed. Skipping codesign verification.'); + process.nextTick(done); + return; } - tasks.push(_.partial(createDMG, INSTALLER_CONFIG)); + debug('Verifying `%s` has been signed correctly...', APP_PATH); + run('codesign', ['--verify', APP_PATH], function(err) { + if (err) { + err = new Error('App is not correctly signed'); + done(err); + return; + } + debug('Verified that the app has been signed correctly!'); + done(); + }); +} + +/** + * Package the application as a single `.DMG` file which + * is the OSX equivalent of a `Setup.exe` installer. + * + * @param {Function} done - Callback which receives `(err)`. + */ +module.exports.installer = function(done) { + debug('creating installer...'); + + var tasks = [ + verify, + _.partial(createDMG, INSTALLER_CONFIG) + ]; series(tasks, function(err) { if (err) { - console.error(err.stack); + console.error(chalk.red.bold(figures.cross), + 'Failed to create DMG installer:', err.message); + console.error(chalk.gray(err.stack)); return done(err); } - console.log('Installer created!'); + console.log(chalk.green.bold(figures.tick), + ' DMG installer written to', + path.join(INSTALLER_CONFIG.out, INSTALLER_CONFIG.name + '.dmg')); done(); }); }; diff --git a/tasks/run.js b/tasks/run.js new file mode 100644 index 00000000000..1fc9a033d15 --- /dev/null +++ b/tasks/run.js @@ -0,0 +1,108 @@ +var fs = require('fs'); +var format = require('util').format; +var spawn = require('child_process').spawn; +var which = require('which'); +var debug = require('debug')('scout:tasks:run'); + +/** + * Use me when you want to run an external command instead + * of using `child_process` directly because I'll handle + * lots of platform edge cases for you and provide + * nice debugging output when things go wrong! + * + * @example + * var run = require('./tasks/run'); + * var args = ['--verify', require('./tasks/darwin').APP_PATH]; + * run('codesign', args, function(err){ + * if(err){ + * console.error('codesign verification failed!'); + * process.exit(1); + * } + * console.log('codesign verification succeeded!'); + * }); + * + * @param {String} cmd - The bin name of your command, e.g. `grep`. + * @param {Array} [args] - Arguments to pass to the command [Default `[]`]. + * @param {Object} [opts] - Options to pass to `child_process.spawn` [Default `{}`]. + * @param {Function} fn - Callback which recieves `(err, output)`. + */ +function run(cmd, args, opts, fn) { + if (typeof opts === 'function') { + fn = opts; + opts = {}; + } + + if (typeof args === 'function') { + fn = args; + args = []; + opts = {}; + } + + getBinPath(cmd, function(err, bin) { + if (err) return fn(err); + + debug('running %j', { + cmd: cmd, + args: args, + opts: opts + }); + var output = []; + + var proc = spawn(bin, args, opts); + proc.stdout.on('data', function(buf) { + debug(' %s> %s', cmd, buf.toString('utf-8')); + output.push(buf); + }); + proc.stderr.on('data', function(buf) { + debug(' %s> %s', cmd, buf.toString('utf-8')); + output.push(buf); + }); + + proc.on('exit', function(code) { + if (code !== 0) { + debug('command failed! %j', { + cmd: cmd, + bin: bin, + args: args, + opts: opts, + code: code + }); + fn(new Error('Command failed! ' + + 'Please try again with debugging enabled.')); + return; + } + debug('completed! %j', { + cmd: cmd, + bin: bin, + args: args, + opts: opts, + code: code + }); + + fn(null, Buffer.concat(output).toString('utf-8')); + }); + }); +} + +/** + * Gets the absolute path for a `cmd`. + * @param {String} cmd - e.g. `codesign`. + * @param {Function} fn - Callback which receives `(err, binPath)`. + * @return {void} + */ +function getBinPath(cmd, fn) { + which(cmd, function(err, bin) { + if (err) return fn(err); + + fs.exists(bin, function(exists) { + if (!exists) { + return fn(new Error(format( + 'Expected file for `%s` does not exist at `%s`', + cmd, bin))); + } + fn(null, bin); + }); + }); +} + +module.exports = run; From d73abf3357eecb1c060f85440a047e51d53346ab Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 30 Sep 2015 16:11:34 -0400 Subject: [PATCH 3/4] :memo: long overdue updates to the README --- README.md | 64 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index bdb2224d17d..84b38a03cc8 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,70 @@ -# compass +# compass [![][travis_img]][travis_url] -Explore your MongoDB. +> Explore your MongoDB. -## Developing +## Development 1. Follow the setup instructions for [OSX][setup-osx], [Windows][setup-windows] or [Linux][setup-linux]. -2. Clone this repo +2. Run `git clone git@github.com:10gen/compass.git ~/compass` to get the source code 3. Run `npm install` to install dependencies -4. Run `npm start` to launch +4. Run `npm start` to build the app and launch it -## Modules +Already setup and prefer a simple copy and paste? + +```bash +git clone git@github.com:10gen/compass.git ~/compass; +cd ~/compass; +npm install; +npm start; +``` + +## Key Modules
-
Compass
+
compass
The default Ampersand.js single-page application people actually interact with. - ScoutClientMixin - connects the models to - scout-client.
-
scout-brain
+
scout-client
- Needs to be broken down into topic based models but for now, this is where - all the business logic code lives we want to share between modules running - in the browser, nodejs, or electron. -
-
scout-client
-
- Provides a clean API for scout-server + Provides a clean API for `compass` to talk to scout-server that works in the browser, nodejs, or electron.
-
scout-server
+
scout-server
- An express.js application which provides REST and socket.io connectivity + An express.js application which provides REST and socket.io endpoints to the mongodb node.js driver.
+
mongodb-connection-model
+
+ A shared Ampersand.js model used by `compass`, `scout-client`, and `scout-server` that encapsulates + all of the business logic for generating valid parameters to hand to the driver to connect to MongoDB. +
+
mongodb-collection-sample
+
+ Provides a single interface for `scout-server` to request a sample of documents from a collection that automatically uses the `$sample` operator if available, falling back to a client-side reservoir sample. +
+
mongodb-schema
+
+ `compass` uses `scout-client` to get a sample of documents from `scout-server` via `mongodb-collection-sample` which is piped into `mongodb-schema` to create a probabilistic representation of what the schema for a given collection most likely is. +
+ ## Building Releases -To compile electron + the app and the installer for your current platform: +After you've made some local changes, the next thing you'll probably want to do +is create an artifact to share. There is only one command you need to run to compile the app, +sign it if the signing certificate is available on your machine, and generate a single file +installer for your current platform: ```bash -npm run release +cd ~/compass; +npm run release; ``` [setup-osx]: https://github.com/mongodb-js/mongodb-js/blob/master/docs/setup.md#osx-setup [setup-windows]: https://github.com/mongodb-js/mongodb-js/blob/master/docs/setup.md#windows-setup [setup-linux]: https://github.com/mongodb-js/mongodb-js/blob/master/docs/setup.md#linux-setup +[travis_img]: https://magnum.travis-ci.com/10gen/compass.svg?token=q2zsnxCbboarF6KYRYxM&branch=master +[travis_url]: https://magnum.travis-ci.com/10gen/compass From ccf9c420cddcc4b277527dcc6f5314d1ff5d2361 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 30 Sep 2015 16:33:03 -0400 Subject: [PATCH 4/4] :memo: add travis badges for all of key modules Is it documentation? Is it a build dashboard? You be the judge. --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 84b38a03cc8..bbb276e91e9 100644 --- a/README.md +++ b/README.md @@ -21,32 +21,31 @@ npm start; ## Key Modules
-
compass
+
 compass
The default Ampersand.js single-page application people actually interact with.
-
scout-client
+
 scout-client
- Provides a clean API for `compass` to talk to scout-server - that works in the browser, nodejs, or electron. + Provides a clean API for `compass` to talk to `scout-server` that works in the browser, nodejs, or electron.
-
scout-server
+
 scout-server
An express.js application which provides REST and socket.io endpoints to the mongodb node.js driver.
-
mongodb-connection-model
+
 mongodb-connection-model
A shared Ampersand.js model used by `compass`, `scout-client`, and `scout-server` that encapsulates all of the business logic for generating valid parameters to hand to the driver to connect to MongoDB.
-
mongodb-collection-sample
+
 mongodb-collection-sample
Provides a single interface for `scout-server` to request a sample of documents from a collection that automatically uses the `$sample` operator if available, falling back to a client-side reservoir sample.
-
mongodb-schema
+
 mongodb-schema
- `compass` uses `scout-client` to get a sample of documents from `scout-server` via `mongodb-collection-sample` which is piped into `mongodb-schema` to create a probabilistic representation of what the schema for a given collection most likely is. + `compass` uses `scout-client` to get a sample of documents from `scout-server` via `mongodb-collection-sample` which is piped into `mongodb-schema` to create a probabilistic representation of what the schema for a given collection most likely is.