From 53436e81b2d289bf91f3e4321a1ce168d0687ea1 Mon Sep 17 00:00:00 2001 From: French Ben Date: Wed, 23 Dec 2015 16:58:10 -0500 Subject: [PATCH 1/2] Updated all electron calls for proper interactions Signed-off-by: French Ben --- package.json | 1 - src/app.js | 20 +++++---- src/browser.js | 42 ++++--------------- src/components/ContainerHomeFolders.react.js | 5 ++- src/components/ContainerListItem.react.js | 5 ++- .../ContainerSettingsGeneral.react.js | 5 ++- .../ContainerSettingsVolumes.react.js | 5 ++- src/components/Header.react.js | 22 ++-------- src/menutemplate.js | 5 ++- src/utils/SetupUtil.js | 1 - src/utils/Util.js | 7 ++-- src/utils/WebUtil.js | 5 ++- 12 files changed, 44 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index a6a361ad7..49caedb2f 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "jsxhint": "^0.15.1", "load-grunt-tasks": "^3.2.0", "minimist": "^1.1.1", - "react-tools": "^0.13.1", "run-sequence": "^1.0.2", "shell-escape": "^0.2.0", "source-map-support": "^0.3.2" diff --git a/src/app.js b/src/app.js index c706a18be..293cf7248 100644 --- a/src/app.js +++ b/src/app.js @@ -1,24 +1,24 @@ require.main.paths.splice(0, 0, process.env.NODE_PATH); -import remote from 'remote'; -var Menu = remote.require('menu'); +import electron from 'electron'; +const remote = electron.remote; +const Menu = remote.Menu; +// ipcRenderer is used as we're in the process +const ipcRenderer = electron.ipcRenderer; + import React from 'react'; -import SetupStore from './stores/SetupStore'; -import ipc from 'ipc'; -import machine from './utils/DockerMachineUtil'; + import metrics from './utils/MetricsUtil'; import template from './menutemplate'; import webUtil from './utils/WebUtil'; import hubUtil from './utils/HubUtil'; import setupUtil from './utils/SetupUtil'; -import request from 'request'; import docker from './utils/DockerUtil'; import hub from './utils/HubUtil'; import Router from 'react-router'; import routes from './routes'; import routerContainer from './router'; import repositoryActions from './actions/RepositoryActions'; -import util from './utils/Util'; -var app = remote.require('app'); +import machine from './utils/DockerMachineUtil'; hubUtil.init(); @@ -47,6 +47,8 @@ var router = Router.create({ router.run(Handler => React.render(, document.body)); routerContainer.set(router); + + setupUtil.setup().then(() => { Menu.setApplicationMenu(Menu.buildFromTemplate(template())); docker.init(); @@ -63,7 +65,7 @@ setupUtil.setup().then(() => { throw err; }); -ipc.on('application:quitting', () => { +ipcRenderer.on('application:quitting', () => { if (localStorage.getItem('settings.closeVMOnQuit') === 'true') { machine.stop(); } diff --git a/src/browser.js b/src/browser.js index 7104cda3c..0ffca63ef 100644 --- a/src/browser.js +++ b/src/browser.js @@ -1,8 +1,10 @@ -import app from 'app'; -import BrowserWindow from 'browser-window'; +import electron from 'electron'; +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + import fs from 'fs'; import os from 'os'; -var ipc = require('electron').ipcMain; + import path from 'path'; import child_process from 'child_process'; @@ -18,29 +20,6 @@ try { settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, 'settings.json'), 'utf8')); } catch (err) {} -let updateCmd = (args, cb) => { - let updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe'); - let child = child_process.spawn(updateExe, args, {detached: true}); - child.on('close', cb); -}; - -if (process.platform === 'win32') { - var squirrelCommand = process.argv[1]; - let target = path.basename(process.execPath); - switch (squirrelCommand) { - case '--squirrel-install': - case '--squirrel-updated': - updateCmd(['--createShortcut', target], app.quit); - break; - case '--squirrel-uninstall': - updateCmd(['--removeShortcut', target], app.quit); - break; - case '--squirrel-obsolete': - app.quit(); - break; - } -} - app.on('ready', function () { var mainWindow = new BrowserWindow({ width: size.width || 1080, @@ -66,25 +45,18 @@ app.on('ready', function () { return false; }); - var updating = false; - ipc.on('application:quit-install', function () { - updating = true; - }); - if (os.platform() === 'win32') { mainWindow.on('close', function () { mainWindow.webContents.send('application:quitting'); return true; }); - app.on('window-all-closed', function() { + app.on('window-all-closed', function () { app.quit(); }); } else if (os.platform() === 'darwin') { app.on('before-quit', function () { - if (!updating) { - mainWindow.webContents.send('application:quitting'); - } + mainWindow.webContents.send('application:quitting'); }); } diff --git a/src/components/ContainerHomeFolders.react.js b/src/components/ContainerHomeFolders.react.js index e56fa451b..cab35902d 100644 --- a/src/components/ContainerHomeFolders.react.js +++ b/src/components/ContainerHomeFolders.react.js @@ -6,8 +6,9 @@ import shell from 'shell'; import util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; import containerActions from '../actions/ContainerActions'; -import remote from 'remote'; -var dialog = remote.require('dialog'); +import electron from 'electron'; +const remote = electron.remote; +const dialog = remote.dialog; import mkdirp from 'mkdirp'; var ContainerHomeFolder = React.createClass({ diff --git a/src/components/ContainerListItem.react.js b/src/components/ContainerListItem.react.js index 76c9d886a..2791ca9d2 100644 --- a/src/components/ContainerListItem.react.js +++ b/src/components/ContainerListItem.react.js @@ -1,8 +1,9 @@ import $ from 'jquery'; import React from 'react/addons'; import Router from 'react-router'; -import remote from 'remote'; -var dialog = remote.require('dialog'); +import electron from 'electron'; +const remote = electron.remote; +const dialog = remote.dialog; import metrics from '../utils/MetricsUtil'; import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import containerActions from '../actions/ContainerActions'; diff --git a/src/components/ContainerSettingsGeneral.react.js b/src/components/ContainerSettingsGeneral.react.js index a9650d43c..8e8066e2c 100644 --- a/src/components/ContainerSettingsGeneral.react.js +++ b/src/components/ContainerSettingsGeneral.react.js @@ -1,8 +1,9 @@ import _ from 'underscore'; import React from 'react/addons'; import metrics from '../utils/MetricsUtil'; -import remote from 'remote'; -var dialog = remote.require('dialog'); +import electron from 'electron'; +const remote = electron.remote; +const dialog = remote.dialog; import ContainerUtil from '../utils/ContainerUtil'; import containerActions from '../actions/ContainerActions'; import util from '../utils/Util'; diff --git a/src/components/ContainerSettingsVolumes.react.js b/src/components/ContainerSettingsVolumes.react.js index c6e42c2d3..914fa5749 100644 --- a/src/components/ContainerSettingsVolumes.react.js +++ b/src/components/ContainerSettingsVolumes.react.js @@ -1,7 +1,8 @@ import _ from 'underscore'; import React from 'react/addons'; -import remote from 'remote'; -var dialog = remote.require('dialog'); +import electron from 'electron'; +const remote = electron.remote; +const dialog = remote.dialog; import shell from 'shell'; import util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; diff --git a/src/components/Header.react.js b/src/components/Header.react.js index d56ec9a7b..4381554a1 100644 --- a/src/components/Header.react.js +++ b/src/components/Header.react.js @@ -1,11 +1,11 @@ import React from 'react/addons'; -import remote from 'remote'; import RetinaImage from 'react-retina-image'; -import ipc from 'ipc'; import util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; -var Menu = remote.require('menu'); -var MenuItem = remote.require('menu-item'); +import electron from 'electron'; +const remote = electron.remote; +const Menu = remote.Menu; +const MenuItem = remote.MenuItem; import accountStore from '../stores/AccountStore'; import accountActions from '../actions/AccountActions'; import Router from 'react-router'; @@ -25,12 +25,6 @@ var Header = React.createClass({ document.addEventListener('keyup', this.handleDocumentKeyUp, false); accountStore.listen(this.update); - - ipc.on('application:update-available', () => { - this.setState({ - updateAvailable: true - }); - }); }, componentWillUnmount: function () { document.removeEventListener('keyup', this.handleDocumentKeyUp, false); @@ -79,10 +73,6 @@ var Header = React.createClass({ handleFullscreenHover: function () { this.update(); }, - handleAutoUpdateClick: function () { - metrics.track('Restarted to Update'); - ipc.send('application:quit-install'); - }, handleUserClick: function (e) { let menu = new Menu(); @@ -166,7 +156,6 @@ var Header = React.createClass({ ); } - let updateWidget = this.state.updateAvailable && !this.props.hideLogin ? UPDATE NOW : null; return (
@@ -174,9 +163,6 @@ var Header = React.createClass({ {username}
-
- {updateWidget} -
{util.isWindows () ? this.renderWindowButtons() : this.renderLogo()}
diff --git a/src/menutemplate.js b/src/menutemplate.js index 043d89239..c6d3b79bc 100644 --- a/src/menutemplate.js +++ b/src/menutemplate.js @@ -1,4 +1,5 @@ -import remote from 'remote'; +import electron from 'electron'; +const remote = electron.remote; import shell from 'shell'; import router from './router'; import util from './utils/Util'; @@ -6,7 +7,7 @@ import metrics from './utils/MetricsUtil'; import machine from './utils/DockerMachineUtil'; import docker from './utils/DockerUtil'; -var app = remote.require('app'); +const app = remote.app; // main.js var MenuTemplate = function () { diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index b6d878765..02faddafb 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -121,7 +121,6 @@ export default { await machine.create(); } else { let state = await machine.status(); - console.log(state); if (state !== 'Running') { if (state === 'Saved') { router.get().transitionTo('setup'); diff --git a/src/utils/Util.js b/src/utils/Util.js index e3418c7c3..c8e376021 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -3,9 +3,10 @@ import Promise from 'bluebird'; import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; -import remote from 'remote'; -var dialog = remote.require('dialog'); -var app = remote.require('app'); +import electron from 'electron'; +const remote = electron.remote; +const dialog = remote.dialog; +const app = remote.app; module.exports = { execFile: function (args, options) { diff --git a/src/utils/WebUtil.js b/src/utils/WebUtil.js index 8bc2f735e..2d774fe01 100644 --- a/src/utils/WebUtil.js +++ b/src/utils/WebUtil.js @@ -1,5 +1,6 @@ -import remote from 'remote'; -var app = remote.require('app'); +import electron from 'electron'; +const remote = electron.remote; +const app = remote.app; import fs from 'fs'; import util from './Util'; import path from 'path'; From bf901c2256baba37fb5f53bb727699924cd240d8 Mon Sep 17 00:00:00 2001 From: French Ben Date: Wed, 23 Dec 2015 20:10:57 -0500 Subject: [PATCH 2/2] Updated tests to pass Signed-off-by: French Ben --- .eslintrc | 1 + .travis.yml | 3 +-- __mocks__/app.js | 5 +++++ __mocks__/electron.js | 7 +++++++ __tests__/Util-test.js | 20 ++++++++++---------- package.json | 4 ++-- 6 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 __mocks__/app.js create mode 100644 __mocks__/electron.js diff --git a/.eslintrc b/.eslintrc index bde0456ec..0f74bd196 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,7 @@ env: node: true es6: true browser: true + jest: true extends: "eslint:recommended" diff --git a/.travis.yml b/.travis.yml index 49d2dc07e..be3929ed9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false language: node_js node_js: - - "4.1" + - "4.2.2" cache: directories: @@ -11,4 +11,3 @@ cache: script: - npm install - npm test - diff --git a/__mocks__/app.js b/__mocks__/app.js new file mode 100644 index 000000000..099bad49b --- /dev/null +++ b/__mocks__/app.js @@ -0,0 +1,5 @@ +module.exports = { + require: jest.genMockFunction(), + match: jest.genMockFunction(), + on: jest.genMockFunction() +}; diff --git a/__mocks__/electron.js b/__mocks__/electron.js new file mode 100644 index 000000000..a2deca4df --- /dev/null +++ b/__mocks__/electron.js @@ -0,0 +1,7 @@ +module.exports = { + require: jest.genMockFunction(), + match: jest.genMockFunction(), + app: jest.genMockFunction(), + remote: jest.genMockFunction(), + dialog: jest.genMockFunction() +}; diff --git a/__tests__/Util-test.js b/__tests__/Util-test.js index 6b4367a8e..e891a5925 100644 --- a/__tests__/Util-test.js +++ b/__tests__/Util-test.js @@ -1,9 +1,9 @@ -jest.dontMock('../src/utils/Util'); -var util = require('../src/utils/Util'); +jest.dontMock('../src/utils/Util').dontMock('console'); +const util = require('../src/utils/Util'); -describe('Util', function () { - describe('when removing sensitive data', function () { - it('filters ssh certificate data', function () { +describe('Util', () => { + describe('when removing sensitive data', () => { + it('filters ssh certificate data', () => { var testdata = String.raw`time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost sudo mkdir -p /var/lib/boot2docker" time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo \"-----BEGIN CERTIFICATE-----\nMIIC+DCCAeKgAwIBAgIRANfIbsa2M94gDY+fBiBiQBkwCwYJKoZIhvcNAQELMBIx\nEDAOBgNVBAoTB2ptb3JnYW4wHhcNMTUwNDE4MDEzODAwWhcNMTgwNDAyMDEzODAw\nWjAPMQ0wCwYDVQQKEwRkZXYyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA1yamWT0bk0pRU7eiStjiXe2jkzdeI0SdJZo+bjczkl6kzNW/FmR/OkcP8gHX\nCO3fUCWkR/+rBgz3nuM1Sy0BIUo0EMQGfx17OqIJPXO+BrpCHsXlphHmbQl5bE2Y\nF+bAsGc6WCippw/caNnIHRsb6zAZVYX2AHLYY0fwIDAQABo1AwTjAOBgNVHQ8BAf8EBAMCAKAwHQYD\nVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0R\nBAgwBocEwKhjZTALBgkqhkiG9w0BAQsDggEBAKBdD86+kl4X1VMjgGlNYnc42tWa\nbo1iDl/frxiLkfPSc2McAOm3AqX1ao+ynjqq1XTlBLPTQByu/oNZgA724LRJDfdG\nCKGUV8latW7rB1yhf/SZSmyhNjufuWlgCtbkw7Q/oPddzYuSOdDW8tVok9gMC0vL\naqKCWfVKkCmvGH+8/wPrkYmro/f0uwJ8ee+yrbBPlBE/qE+Lqcfr0YcXEDaS8CmL\nDjWg7KNFpA6M+/tFNQhplbjwRsCt7C4bzQu0aBIG5XH1Jr2HrKlLjWdmluPHWUL6\nX5Vh1bslYJzsSdBNZFWSKShZ+gtRpjtV7NynANDJPQNIRhDxAf4uDY9hA2c=\n-----END CERTIFICATE-----\n\" | sudo tee /var/lib/boot2docker/server.pem" time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: /usr/bin/VBoxManage showvminfo dev2 --machinereadable"`; expect(util.removeSensitiveData(testdata).indexOf('CERTIFICATE')).toEqual(-1); @@ -11,7 +11,7 @@ describe('Util', function () { expect(util.removeSensitiveData(testdata).indexOf('')).toNotEqual(-1); }); - it('filters ssh private key data', function () { + it('filters ssh private key data', () => { var testdata = String.raw`hZbuxglOtQv2AQqOp/luhZ3Y8kDs4cqRzoA1o+k+LAyjEb+Nk\nGA8=\n-----END CERTIFICATE-----\n\" | sudo tee /var/lib/boot2docker/ca.pem" time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo \"-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1yamWT0bk0pRU7eiStjiXe2jkzdeI0SdJZo+bjczkl6kzNW/\nFmR/OkcP8gHXCO3fUCWkR/+rBgz3nuM1Sy0BIUo0EMQGfx17OqIJPXO+BrpCHsXl\nphHmbQl5bE2YF+bAsGc6WCippczQIu5bPweeAkR1WdlkhD08tHD4o1ESe09fXx5G\nXcZFfd2xQWdvAJX3fTuGBk3IMEF2fye5b69zUyVDGbTylyjKDOi9Xxdlc4y9cOPw\nzcwQFCOJiCBYlxDO0fbinA+KigCs29Dd5U3oXbloLr3JQTE/SkxFh9W5rkX8ysY4\n2h3EnR7YIBWt/caNnIHRsb6zAZVYX2AHLYY0fwIDAQABAoIBAQDKF3TTh/G59WnU\n4D2iXnyqy8gFRVG4gP+3TV3s+w8HIr1b5j6akwVqwUs5//5zVbSYPPNF6eJESbPi\nW/s4ROq10VR8lxSfHBsfJQrW3TwWZ6gp7atbxZ6Stv6F+5CsisReLmiAXJmVsn+j\nAA9Xchk6egFcxzWCfV7jAuaZyVI53cclepm/xkGjPwrfXr+nA+UMvO6DllC6IcBF\no4+O0jVtzdMecZnQk6nWxNJjurodTTQakrNAqSMgBshn48wf3N35b+p8RtTzLJ8L\nYuHkv6OKMITIazcHadjsN8icGgIGf2BJ1CRje7j0Yzow8jwY+Pet3yxKSfXED89B\nD34AEXl5AoGBANi17og+yPFOWURUrksO/QyzlOtXcQdQu8SmkUj4ACoqF0gegQIb\nC/DNMcYxJAsPPgw/t5Ws/af8DuatYguGukmekYREVjc7DS/hPWDZzeavPd95cOw0\nuMPgJE76HJ3BSYcp1f8WKcN+xDket9CF6Qz+VX5aQSUEc333V5h7D/nzAoGBAP4o\nVCvQu5eKYmDhMFSOA0+Qm3EECRqMLoH6kpEcbMjM8+kOeI0fUuE3CX8nzs7P4py/\n0IFj2Yxl578NHJOjCpbB1UKtxLkmDH42wXXzrWJXRaWXC93dh1sl0aB6qE25FtSD\nzjYh4y1DA/t6y95YRrIqC2WhIU7eigIoujmtOFJFAoGABSKiiWX7ewRhRyY+jxbG\n1lM3FzCWRBccq/dKgBEoZ9dhf9sBMZyUdttV751gfkaZMM8duZVE2YM2ky7OoPlL\nVs1EI38/D8X9dQIAY1gl8e57J92H2IETU8ju81Qn83EOHf7WzFmpGbHaUoQw1Ocn\nc6BfREQ9QPRPDFAdKkbYRRMCgYEAl44k4xvNQUhb8blWwJUOlFt+1Z26cAI3mXp5\n+94fYH4W1Fq0uDJ9kZ7oItLyF5EPaLlY9E8+YuJBl0OSTtdicROUv/Yu4Nk3ievM\n4TE1qvavqVaw1NRM6qVao3+A7Rf57S/Lv6vldBAKR+OpviSVw5gew7OZ0RYS5caz\nhcEtXKECgYAJb7t67nococm0PsRe8Xv1SQOQjetrhzwzD1PLOSC9TrzwA22/ZktZ\neu/qfvYgOPT4LkDGVCzn8J+TAcUVnIvAnJRQTsBu55uiL8YC5jZQ8E1hBf7kskMq\nh16WD19Djv3WhfBNXBxvnagDDWw5DxmiiKzSf0k3QDDoX7wjDAV1dQ==\n-----END RSA PRIVATE KEY-----\n\" | sudo tee /var/lib/boot2docker/server-key.pem" time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo \"-----BEGIN CERTIFICATE-----\nMIIC+DCCAeKgAwIBAgIRANfIbsa2M94gDY+fBiBiQBkwCwYJKoZIhvcNAQELMBIx\nEDAOBg`; @@ -20,7 +20,7 @@ describe('Util', function () { expect(util.removeSensitiveData(testdata).indexOf('')).toNotEqual(-1); }); - it('filters username data', function () { + it('filters username data', () => { var testdata = String.raw`/Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo`; expect(util.removeSensitiveData(testdata).indexOf('/Users/johnappleseed/')).toEqual(-1); expect(util.removeSensitiveData(testdata).indexOf('/Users//')).toNotEqual(-1); @@ -30,20 +30,20 @@ describe('Util', function () { expect(util.removeSensitiveData(testdata).indexOf('/Users//.docker')).toNotEqual(-1); }); - it('filters Windows username data', function () { + it('filters Windows username data', () => { var testdata = String.raw`C:\\Users\\johnappleseed\\.docker\\machine`; expect(util.removeSensitiveData(testdata).indexOf('johnappleseed')).toEqual(-1); expect(util.removeSensitiveData(testdata).indexOf('')).toNotEqual(-1); }); - it ('returns input if empty or not a string', function () { + it ('returns input if empty or not a string', () => { expect(util.removeSensitiveData('')).toBe(''); expect(util.removeSensitiveData(1)).toBe(1); expect(util.removeSensitiveData(undefined)).toBe(undefined); }); }); - describe('when verifying that a repo is official', function () { + describe('when verifying that a repo is official', () => { it('accepts official repo', () => { expect(util.isOfficialRepo('redis')).toBe(true); }); diff --git a/package.json b/package.json index 49caedb2f..c61c40a67 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "devDependencies": { "babel": "^5.8.23", "babel-jest": "^5.2.0", - "electron-prebuilt": "^0.35.4", + "electron-prebuilt": "^0.36", "eslint": "^1.3.1", "eslint-plugin-react": "^3.3.0", "grunt": "^0.4.5", @@ -79,7 +79,7 @@ "grunt-rename": "^0.1.4", "grunt-shell": "^1.1.2", "grunt-shell-spawn": "^0.3.8", - "jest-cli": "^0.5.8", + "jest-cli": "^0.8.2", "jsxhint": "^0.15.1", "load-grunt-tasks": "^3.2.0", "minimist": "^1.1.1",