diff --git a/README.md b/README.md index ae738c4c2b..854c01e358 100755 --- a/README.md +++ b/README.md @@ -25,6 +25,16 @@ To run the app in development: - `npm run release` +### Unit Tests + +- `npm test` + +### Integration Tests + +Note that integration tests need to be run a Mac and _will_ remove your existing Boot2Docker VM, containers etc. + +- `npm run test:integration` + ## Uninstalling - Remove Kitematic.app diff --git a/app/Setup.react.js b/app/Setup.react.js deleted file mode 100644 index 5c07232533..0000000000 --- a/app/Setup.react.js +++ /dev/null @@ -1,218 +0,0 @@ -var React = require('react/addons'); -var Router = require('react-router'); -var Radial = require('./Radial.react.js'); -var async = require('async'); -var assign = require('object-assign'); -var fs = require('fs'); -var path = require('path'); -var boot2docker = require('./boot2docker.js'); -var virtualbox = require('./virtualbox.js'); -var util = require('./util.js'); -var docker = require('./docker.js'); -var ContainerStore = require('./ContainerStore.js'); - -var setupSteps = [ - { - run: function (callback, progressCallback) { - console.log(util.supportDir()); - var installed = virtualbox.installed(); - if (!installed) { - util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(util.supportDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { - if (err) {callback(err); return;} - virtualbox.install(function (err) { - if (!virtualbox.installed()) { - callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.'); - } else { - callback(err); - } - }); - }, function (progress) { - progressCallback(progress); - }); - } else { - virtualbox.version(function (err, installedVersion) { - if (err) {callback(err); return;} - if (util.compareVersions(installedVersion, virtualbox.REQUIRED_VERSION) < 0) { - // Download a newer version of Virtualbox - util.downloadFile(Setup.BASE_URL + virtualbox.INSTALLER_FILENAME, path.join(util.getResourceDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { - if (err) {callback(err); return;} - virtualbox.kill(function (err) { - if (err) {callback(err); return;} - virtualbox.install(function (err) { - if (err) {callback(err); return;} - virtualbox.version(function (err, installedVersion) { - if (err) {callback(err); return;} - if (util.compareVersions(installedVersion, virtualbox.REQUIRED_VERSION) < 0) { - callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.'); - } else { - callback(err); - } - }); - }); - }); - }, function (progress) { - progressCallback(progress); - }); - } else { - callback(); - } - }); - } - }, - message: 'Downloading VirtualBox...' - }, - { - run: function (callback) { - virtualbox.deleteVM('kitematic-vm', function (err, removed) { - if (err) { - console.log(err); - } - callback(); - }); - }, - message: 'Cleaning up existing Docker VM...' - }, - - // Initialize Boot2Docker if necessary. - { - run: function (callback) { - boot2docker.exists(function (err, exists) { - if (err) { callback(err); return; } - if (!exists) { - boot2docker.init(function (err) { - callback(err); - }); - } else { - if (!boot2docker.sshKeyExists()) { - callback('Boot2Docker SSH key doesn\'t exist. Fix by removing the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); - } else { - boot2docker.isoVersion(function (err, version) { - if (err || util.compareVersions(version, boot2docker.version()) < 0) { - boot2docker.stop(function(err) { - boot2docker.upgrade(function (err) { - callback(err); - }); - }); - } else { - callback(); - } - }); - } - } - }); - }, - message: 'Setting up the Docker VM...' - }, - { - run: function (callback) { - boot2docker.waitWhileStatus('saving', function (err) { - boot2docker.status(function (err, status) { - if (err) {callback(err); return;} - if (status !== 'running') { - boot2docker.start(function (err) { - callback(err); - }); - } else { - callback(); - } - }); - }); - }, - message: 'Starting the Docker VM...' - }, - { - run: function (callback) { - boot2docker.ip(function (err, ip) { - if (err) {callback(err); return;} - console.log('Setting host IP to: ' + ip); - docker.setHost(ip); - callback(err); - }); - }, - message: 'Detecting Docker VM...' - } -]; - -var Setup = React.createClass({ - mixins: [ Router.Navigation ], - getInitialState: function () { - return { - message: '', - progress: 0 - }; - }, - render: function () { - var radial; - if (this.state.progress) { - radial = ; - } else if (this.state.error) { - radial = ; - } else { - radial = ; - } - if (this.state.error) { - return ( -
- {radial} -

Error: {this.state.error}

-
- ); - } else { - return ( -
- {radial} -

{this.state.message}

-
- ); - } - }, - componentWillMount: function () { - this.setState({}); - }, - componentDidMount: function () { - var self = this; - this.setup(function (err) { - if (!err) { - boot2docker.ip(function (err, ip) { - docker.setHost(ip); - ContainerStore.init(function () { - self.transitionTo('containers'); - }); - }); - } - }); - }, - setup: function (callback) { - var self = this; - var currentStep = 0; - async.eachSeries(setupSteps, function (step, callback) { - console.log('Performing step ' + currentStep); - self.setState({progress: 0}); - self.setState({message: step.message}); - step.run(function (err) { - if (err) { - callback(err); - } else { - currentStep += 1; - callback(); - } - }, function (progress) { - self.setState({progress: progress}); - }); - }, function (err) { - if (err) { - // if any of the steps fail - console.log('Kitematic setup failed at step ' + currentStep); - console.log(err); - self.setState({error: err.message}); - callback(err); - } else { - // Setup Finished - console.log('Setup finished.'); - callback(); - } - }); - } -}); - -module.exports = Setup; diff --git a/browser/main.js b/browser/main.js index 21bec8ecd9..ee51d8d578 100644 --- a/browser/main.js +++ b/browser/main.js @@ -11,14 +11,18 @@ var BrowserWindow = require('browser-window'); var ipc = require('ipc'); var argv = require('minimist')(process.argv); - -if (argv.test) { - console.log('Running tests'); -} +var saveVMOnQuit = false; process.env.NODE_PATH = __dirname + '/../node_modules'; +process.env.RESOURCES_PATH = __dirname + '/../resources'; process.chdir(path.join(__dirname, '..')); +if (argv.integration) { + process.env.TEST_TYPE = 'integration'; +} else { + process.env.TEST_TYPE = 'test'; +} + app.on('activate-with-no-open-windows', function () { if (mainWindow) { mainWindow.show(); @@ -27,81 +31,78 @@ app.on('activate-with-no-open-windows', function () { }); app.on('ready', function() { - var windowOptions = { + mainWindow = new BrowserWindow({ width: 1000, height: 700, 'min-width': 1000, 'min-height': 700, resizable: true, - frame: false - }; - - mainWindow = new BrowserWindow(windowOptions); - mainWindow.hide(); + frame: false, + show: false + }); if (argv.test) { - mainWindow.loadUrl('file://' + __dirname + '/../build/specs.html'); + mainWindow.loadUrl('file://' + __dirname + '/../tests/tests.html'); } else { mainWindow.loadUrl('file://' + __dirname + '/../build/index.html'); + app.on('will-quit', function (e) { + if (saveVMOnQuit) { + exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); + } + }); } - process.on('uncaughtException', app.quit); - - var saveVMOnQuit = false; - app.on('will-quit', function (e) { - if (saveVMOnQuit) { - exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); - } - }); - mainWindow.webContents.on('new-window', function (e) { e.preventDefault(); }); mainWindow.webContents.on('did-finish-load', function() { - mainWindow.show(); + if (!argv.test) { + mainWindow.show(); + } mainWindow.focus(); - mainWindow.setTitle(''); // Auto Updates - autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion()); + if (process.env.NODE_ENV !== 'development' && !argv.test) { + autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion()); - autoUpdater.on('checking-for-update', function (e) { - console.log('Checking for update...'); - }); + autoUpdater.on('checking-for-update', function (e) { + console.log('Checking for update...'); + }); - autoUpdater.on('update-available', function (e) { - console.log('Update available.'); - console.log(e); - }); + autoUpdater.on('update-available', function (e) { + console.log('Update available.'); + console.log(e); + }); - autoUpdater.on('update-not-available', function (e) { - console.log('Update not available.'); - }); + autoUpdater.on('update-not-available', function (e) { + console.log('Update not available.'); + }); - autoUpdater.on('update-downloaded', function (e, releaseNotes, releaseName, releaseDate, updateURL) { - console.log('Update downloaded.'); - mainWindow.webContents.send('notify', 'window:update-available'); - }); + autoUpdater.on('update-downloaded', function (e, releaseNotes, releaseName, releaseDate, updateURL) { + console.log('Update downloaded.'); + mainWindow.webContents.send('notify', 'window:update-available'); + }); - autoUpdater.on('error', function (e) { - console.log('An error occured while checking for updates.'); - console.log(e); - }); + autoUpdater.on('error', function (e) { + console.log('An error occured while checking for updates.'); + console.log(e); + }); - ipc.on('command', function (event, arg) { - console.log('Command: ' + arg); - if (arg === 'application:quit-install') { - saveVMOnQuit = false; - autoUpdater.quitAndInstall(); - } - }); + ipc.on('command', function (event, arg) { + console.log('Command: ' + arg); + if (arg === 'application:quit-install') { + saveVMOnQuit = false; + autoUpdater.quitAndInstall(); + } + }); + + autoUpdater.checkForUpdates(); + } ipc.on('vm', function (event, arg) { saveVMOnQuit = arg; }); - - autoUpdater.checkForUpdates(); }); }); diff --git a/deps b/deps index 059abaf35c..7e1cbc3cc2 100755 --- a/deps +++ b/deps @@ -1,18 +1,15 @@ #!/bin/bash - BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") -export BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION - -mkdir -p $BASE/cache +BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") +BOOT2DOCKER_CLI_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION pushd $BASE/resources > /dev/null -if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then +if [ ! -f $BOOT2DOCKER_CLI_FILE ]; then echo "-----> Downloading Boot2docker CLI..." rm -rf boot2docker-* - curl -L -o $BOOT2DOCKER_CLI_VERSION_FILE https://github.com/boot2docker/boot2docker-cli/releases/download/v${BOOT2DOCKER_CLI_VERSION}/boot2docker-v${BOOT2DOCKER_CLI_VERSION}-darwin-amd64 - chmod +x $BOOT2DOCKER_CLI_VERSION_FILE + curl -L -o $BOOT2DOCKER_CLI_FILE https://github.com/boot2docker/boot2docker-cli/releases/download/v${BOOT2DOCKER_CLI_VERSION}/boot2docker-v${BOOT2DOCKER_CLI_VERSION}-darwin-amd64 + chmod +x $BOOT2DOCKER_CLI_FILE fi popd > /dev/null diff --git a/app/fonts/clearsans-bold-webfont.ttf b/fonts/clearsans-bold-webfont.ttf similarity index 100% rename from app/fonts/clearsans-bold-webfont.ttf rename to fonts/clearsans-bold-webfont.ttf diff --git a/app/fonts/clearsans-bolditalic-webfont.ttf b/fonts/clearsans-bolditalic-webfont.ttf similarity index 100% rename from app/fonts/clearsans-bolditalic-webfont.ttf rename to fonts/clearsans-bolditalic-webfont.ttf diff --git a/app/fonts/clearsans-italic-webfont.ttf b/fonts/clearsans-italic-webfont.ttf similarity index 100% rename from app/fonts/clearsans-italic-webfont.ttf rename to fonts/clearsans-italic-webfont.ttf diff --git a/app/fonts/clearsans-light-webfont.ttf b/fonts/clearsans-light-webfont.ttf similarity index 100% rename from app/fonts/clearsans-light-webfont.ttf rename to fonts/clearsans-light-webfont.ttf diff --git a/app/fonts/clearsans-medium-webfont.ttf b/fonts/clearsans-medium-webfont.ttf similarity index 100% rename from app/fonts/clearsans-medium-webfont.ttf rename to fonts/clearsans-medium-webfont.ttf diff --git a/app/fonts/clearsans-mediumitalic-webfont.ttf b/fonts/clearsans-mediumitalic-webfont.ttf similarity index 100% rename from app/fonts/clearsans-mediumitalic-webfont.ttf rename to fonts/clearsans-mediumitalic-webfont.ttf diff --git a/app/fonts/clearsans-regular-webfont.ttf b/fonts/clearsans-regular-webfont.ttf similarity index 100% rename from app/fonts/clearsans-regular-webfont.ttf rename to fonts/clearsans-regular-webfont.ttf diff --git a/app/fonts/clearsans-thin-webfont.ttf b/fonts/clearsans-thin-webfont.ttf similarity index 100% rename from app/fonts/clearsans-thin-webfont.ttf rename to fonts/clearsans-thin-webfont.ttf diff --git a/app/fonts/streamline-24px.eot b/fonts/streamline-24px.eot similarity index 100% rename from app/fonts/streamline-24px.eot rename to fonts/streamline-24px.eot diff --git a/app/fonts/streamline-24px.svg b/fonts/streamline-24px.svg similarity index 100% rename from app/fonts/streamline-24px.svg rename to fonts/streamline-24px.svg diff --git a/app/fonts/streamline-24px.ttf b/fonts/streamline-24px.ttf similarity index 100% rename from app/fonts/streamline-24px.ttf rename to fonts/streamline-24px.ttf diff --git a/app/fonts/streamline-24px.woff b/fonts/streamline-24px.woff similarity index 100% rename from app/fonts/streamline-24px.woff rename to fonts/streamline-24px.woff diff --git a/app/ContainerDetailsSettings.react.js b/gulp similarity index 100% rename from app/ContainerDetailsSettings.react.js rename to gulp diff --git a/gulpfile.js b/gulpfile.js index 7468cada7b..546108ce42 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,90 +1,50 @@ -var gulp = require('gulp'); -var source = require('vinyl-source-stream'); // Used to stream bundle for further handling -var browserify = require('browserify'); -var watchify = require('watchify'); -var reactify = require('reactify'); -var gulpif = require('gulp-if'); -var uglify = require('gulp-uglifyjs'); -var notify = require('gulp-notify'); var concat = require('gulp-concat'); +var cssmin = require('gulp-cssmin'); +var downloadatomshell = require('gulp-download-atom-shell'); +var fs = require('fs'); +var gulp = require('gulp'); +var gulpif = require('gulp-if'); +var gutil = require('gulp-util'); +var http = require('http'); var less = require('gulp-less'); var livereload = require('gulp-livereload'); -var cssmin = require('gulp-cssmin'); -var imagemin = require('gulp-imagemin'); -var gutil = require('gulp-util'); -var shell = require('gulp-shell'); var plumber = require('gulp-plumber'); -var sourcemaps = require('gulp-sourcemaps'); -var glob = require('glob'); -var runSequence = require('run-sequence'); -var ecstatic = require('ecstatic'); -var downloadatomshell = require('gulp-download-atom-shell'); -var packagejson = require('./package.json'); -var http = require('http'); var react = require('gulp-react'); -var fs = require('fs'); +var runSequence = require('run-sequence'); +var shell = require('gulp-shell'); +var sourcemaps = require('gulp-sourcemaps'); +var packagejson = require('./package.json'); var dependencies = Object.keys(packagejson.dependencies); var devDependencies = Object.keys(packagejson.devDependencies); var options = { dev: process.argv.indexOf('release') === -1 && process.argv.indexOf('test') === -1, test: process.argv.indexOf('test') !== -1, + integration: process.argv.indexOf('--integration') !== -1, filename: 'Kitematic.app', name: 'Kitematic' }; gulp.task('js', function () { - gulp.src('./app/**/*.js') + gulp.src('src/**/*.js') .pipe(plumber(function(error) { gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); // emit the end event, to properly end the task this.emit('end'); })) .pipe(react()) - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulp.dest((options.dev || options.test) ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); }); -gulp.task('specs', function () { - var bundler = browserify({ - entries: glob.sync('./specs/**/*-spec.js'), - debug: true, // Gives us sourcemapping - transform: [reactify], - cache: {}, packageCache: {}, fullPaths: true // Requirement of watchify - }); - - dependencies.forEach(function (dep) { - bundler.external(dep); - }); - - devDependencies.forEach(function (dep) { - bundler.external(dep); - }); - - bundler.external('./app'); - - bundler.bundle() - .on('error', gutil.log) - .pipe(source('specs.js')) - .pipe(gulp.dest('./build')); - - gulp.src('./specs/specs.html') - .pipe(gulp.dest('./build')); -}); - gulp.task('images', function() { - return gulp.src('./app/images/*') - .pipe(imagemin({ - progressive: true, - interlaced: true, - svgoPlugins: [{removeViewBox: false}] - })) + return gulp.src('images/*') .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); }); gulp.task('styles', function () { - return gulp.src('app/styles/main.less') + return gulp.src('styles/main.less') .pipe(plumber(function(error) { gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); // emit the end event, to properly end the task @@ -96,7 +56,7 @@ gulp.task('styles', function () { .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) .pipe(gulpif(!options.dev, cssmin())) .pipe(concat('main.css')) - .pipe(gulpif(options.dev && !options.test, livereload())); + .pipe(gulpif(options.dev, livereload())); }); gulp.task('download', function (cb) { @@ -107,11 +67,11 @@ gulp.task('download', function (cb) { }); gulp.task('copy', function () { - gulp.src('./app/index.html') + gulp.src('index.html') .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); - gulp.src('./app/fonts/**') + gulp.src('fonts/**') .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); }); @@ -180,19 +140,25 @@ gulp.task('release', function () { runSequence('download', 'dist', ['copy', 'images', 'js', 'styles'], 'sign', 'zip'); }); -gulp.task('test', ['download', 'copy', 'js', 'images', 'styles', 'specs'], function () { +gulp.task('test', ['download', 'copy', 'js'], function () { var env = process.env; - env.NODE_ENV = 'development'; - gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test'], { - env: env - })); + env.NODE_ENV = 'test'; + if (options.integration) { + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test --integration'], { + env: env + })); + } else { + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test'], { + env: env + })); + } }); gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () { - gulp.watch('./app/**/*.js', ['js']); - gulp.watch('./app/**/*.html', ['copy']); - gulp.watch('./app/styles/**/*.less', ['styles']); - gulp.watch('./app/images/**', ['images']); + gulp.watch('src/**/*.js', ['js']); + gulp.watch('index.html', ['copy']); + gulp.watch('styles/**/*.less', ['styles']); + gulp.watch('images/**', ['images']); livereload.listen(); diff --git a/app/images/close.png b/images/close.png similarity index 100% rename from app/images/close.png rename to images/close.png diff --git a/app/images/close@2x.png b/images/close@2x.png similarity index 100% rename from app/images/close@2x.png rename to images/close@2x.png diff --git a/app/images/downloading-arrow-white.png b/images/downloading-arrow-white.png similarity index 100% rename from app/images/downloading-arrow-white.png rename to images/downloading-arrow-white.png diff --git a/app/images/downloading-arrow-white@2x.png b/images/downloading-arrow-white@2x.png similarity index 100% rename from app/images/downloading-arrow-white@2x.png rename to images/downloading-arrow-white@2x.png diff --git a/app/images/downloading-arrow.png b/images/downloading-arrow.png similarity index 100% rename from app/images/downloading-arrow.png rename to images/downloading-arrow.png diff --git a/app/images/downloading-arrow@2x.png b/images/downloading-arrow@2x.png similarity index 100% rename from app/images/downloading-arrow@2x.png rename to images/downloading-arrow@2x.png diff --git a/app/images/downloading-white.png b/images/downloading-white.png similarity index 100% rename from app/images/downloading-white.png rename to images/downloading-white.png diff --git a/app/images/downloading-white@2x.png b/images/downloading-white@2x.png similarity index 100% rename from app/images/downloading-white@2x.png rename to images/downloading-white@2x.png diff --git a/app/images/downloading.png b/images/downloading.png similarity index 100% rename from app/images/downloading.png rename to images/downloading.png diff --git a/app/images/downloading@2x.png b/images/downloading@2x.png similarity index 100% rename from app/images/downloading@2x.png rename to images/downloading@2x.png diff --git a/app/images/error.png b/images/error.png similarity index 100% rename from app/images/error.png rename to images/error.png diff --git a/app/images/error@2x.png b/images/error@2x.png similarity index 100% rename from app/images/error@2x.png rename to images/error@2x.png diff --git a/app/images/fullscreen.png b/images/fullscreen.png similarity index 100% rename from app/images/fullscreen.png rename to images/fullscreen.png diff --git a/app/images/fullscreen@2x.png b/images/fullscreen@2x.png similarity index 100% rename from app/images/fullscreen@2x.png rename to images/fullscreen@2x.png diff --git a/app/images/fullscreenclose.png b/images/fullscreenclose.png similarity index 100% rename from app/images/fullscreenclose.png rename to images/fullscreenclose.png diff --git a/app/images/loading.png b/images/loading.png similarity index 100% rename from app/images/loading.png rename to images/loading.png diff --git a/app/images/loading@2x.png b/images/loading@2x.png similarity index 100% rename from app/images/loading@2x.png rename to images/loading@2x.png diff --git a/app/images/minimize.png b/images/minimize.png similarity index 100% rename from app/images/minimize.png rename to images/minimize.png diff --git a/app/images/minimize@2x.png b/images/minimize@2x.png similarity index 100% rename from app/images/minimize@2x.png rename to images/minimize@2x.png diff --git a/app/images/official.png b/images/official.png similarity index 100% rename from app/images/official.png rename to images/official.png diff --git a/app/images/official@2x.png b/images/official@2x.png similarity index 100% rename from app/images/official@2x.png rename to images/official@2x.png diff --git a/app/images/paused.png b/images/paused.png similarity index 100% rename from app/images/paused.png rename to images/paused.png diff --git a/app/images/paused@2x.png b/images/paused@2x.png similarity index 100% rename from app/images/paused@2x.png rename to images/paused@2x.png diff --git a/app/images/restarting.png b/images/restarting.png similarity index 100% rename from app/images/restarting.png rename to images/restarting.png diff --git a/app/images/restarting@2x.png b/images/restarting@2x.png similarity index 100% rename from app/images/restarting@2x.png rename to images/restarting@2x.png diff --git a/app/images/roundedcontainer.png b/images/roundedcontainer.png similarity index 100% rename from app/images/roundedcontainer.png rename to images/roundedcontainer.png diff --git a/app/images/roundedcontainer@2x.png b/images/roundedcontainer@2x.png similarity index 100% rename from app/images/roundedcontainer@2x.png rename to images/roundedcontainer@2x.png diff --git a/app/images/running-white.png b/images/running-white.png similarity index 100% rename from app/images/running-white.png rename to images/running-white.png diff --git a/app/images/running-white@2x.png b/images/running-white@2x.png similarity index 100% rename from app/images/running-white@2x.png rename to images/running-white@2x.png diff --git a/app/images/running.png b/images/running.png similarity index 100% rename from app/images/running.png rename to images/running.png diff --git a/app/images/running@2x.png b/images/running@2x.png similarity index 100% rename from app/images/running@2x.png rename to images/running@2x.png diff --git a/app/images/runningwave-white.png b/images/runningwave-white.png similarity index 100% rename from app/images/runningwave-white.png rename to images/runningwave-white.png diff --git a/app/images/runningwave-white@2x.png b/images/runningwave-white@2x.png similarity index 100% rename from app/images/runningwave-white@2x.png rename to images/runningwave-white@2x.png diff --git a/app/images/runningwave.png b/images/runningwave.png similarity index 100% rename from app/images/runningwave.png rename to images/runningwave.png diff --git a/app/images/runningwave@2x.png b/images/runningwave@2x.png similarity index 100% rename from app/images/runningwave@2x.png rename to images/runningwave@2x.png diff --git a/app/images/still-white.png b/images/still-white.png similarity index 100% rename from app/images/still-white.png rename to images/still-white.png diff --git a/app/images/still-white@2x.png b/images/still-white@2x.png similarity index 100% rename from app/images/still-white@2x.png rename to images/still-white@2x.png diff --git a/app/images/stopped-white.png b/images/stopped-white.png similarity index 100% rename from app/images/stopped-white.png rename to images/stopped-white.png diff --git a/app/images/stopped-white@2x.png b/images/stopped-white@2x.png similarity index 100% rename from app/images/stopped-white@2x.png rename to images/stopped-white@2x.png diff --git a/app/images/stopped.png b/images/stopped.png similarity index 100% rename from app/images/stopped.png rename to images/stopped.png diff --git a/app/images/stopped@2x.png b/images/stopped@2x.png similarity index 100% rename from app/images/stopped@2x.png rename to images/stopped@2x.png diff --git a/app/images/wavy-white.png b/images/wavy-white.png similarity index 100% rename from app/images/wavy-white.png rename to images/wavy-white.png diff --git a/app/images/wavy-white@2x.png b/images/wavy-white@2x.png similarity index 100% rename from app/images/wavy-white@2x.png rename to images/wavy-white@2x.png diff --git a/app/index.html b/index.html similarity index 89% rename from app/index.html rename to index.html index 699c7186f0..86d233b8db 100644 --- a/app/index.html +++ b/index.html @@ -6,6 +6,6 @@ Kitematic - + diff --git a/package.json b/package.json index e11677079d..9371f56922 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,11 @@ "bugs": "https://github.com/kitematic/kitematic/issues", "scripts": { "start": "gulp", - "preinstall": "./deps", - "test": "gulp test", - "release": "gulp release" + "test": "gulp test --silent", + "test:integration": "gulp test --silent --integration", + "all-tests": "npm test && npm run integration-tests", + "release": "gulp run release", + "preinstall": "./deps" }, "licenses": [ { @@ -22,18 +24,27 @@ "url": "https://raw.githubusercontent.com/kitematic/kitematic/master/LICENSE" } ], - "boot2docker-version": "1.3.2", - "atom-shell-version": "0.20.6", + "jest": { + "unmockedModulePathPatterns": [ + "dockerode", + "react", + "debug" + ] + }, + "boot2docker-version": "1.4.1", + "atom-shell-version": "0.21.0", + "virtualbox-version": "4.3.20", + "virtualbox-filename": "VirtualBox-4.3.20-96996-OSX.dmg", + "virtualbox-required-version": "4.3.18", "dependencies": { "ansi-to-html": "0.2.0", "async": "^0.9.0", "bugsnag-js": "git+https://git@github.com/bugsnag/bugsnag-js", "dockerode": "2.0.4", "exec": "0.1.2", - "gulp-react": "^2.0.0", + "jest-cli": "^0.2.1", "jquery": "^2.1.3", "minimist": "^1.1.0", - "moment": "2.8.1", "node-uuid": "1.4.1", "object-assign": "^2.0.0", "open": "0.0.5", @@ -41,7 +52,7 @@ "react-bootstrap": "^0.13.2", "react-retina-image": "^1.1.2", "react-router": "^0.11.6", - "request": "2.42.0", + "request": "^2.51.0", "request-progress": "0.3.1", "retina.js": "^1.1.0", "underscore": "^1.7.0" @@ -61,17 +72,19 @@ "gulp-livereload": "^2.1.1", "gulp-notify": "^1.4.2", "gulp-plumber": "^0.6.6", + "gulp-react": "^2.0.0", "gulp-shell": "^0.2.11", "gulp-sourcemaps": "^1.2.8", "gulp-streamify": "0.0.5", "gulp-uglify": "^0.3.1", "gulp-uglifyjs": "^0.5.0", "gulp-util": "^3.0.0", - "jasmine-tagged": "^1.1.2", - "livereload-js": "^2.2.1", "reactify": "^0.15.2", + "rimraf": "^2.2.8", "run-sequence": "^1.0.2", + "time-require": "^0.1.2", "vinyl-source-stream": "^0.1.1", - "watchify": "^2.1.1" + "watchify": "^2.1.1", + "zombie": "^2.5.1" } } diff --git a/resources/cocoasudo b/resources/cocoasudo new file mode 100755 index 0000000000..ccf0bf8aa1 Binary files /dev/null and b/resources/cocoasudo differ diff --git a/resources/virtualbox-4.3.18.pkg b/resources/virtualbox-4.3.18.pkg deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/specs/App-spec.js b/specs/App-spec.js deleted file mode 100644 index cc2a1f9d26..0000000000 --- a/specs/App-spec.js +++ /dev/null @@ -1,11 +0,0 @@ -var Containers = require('./../app/Containers.react.js'); -var TestUtils = require('react/addons').TestUtils; -var jasmine = require('jasmine-node'); - -describe('Containers', function() { - it('should be wrapped with a div', function() { - // var app = TestUtils.renderIntoDocument(App()); - expect(true).toEqual(true); - }); - -}); diff --git a/specs/specs.html b/specs/specs.html deleted file mode 100644 index ffec40e8ea..0000000000 --- a/specs/specs.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/boot2docker.js b/src/Boot2Docker.js similarity index 96% rename from app/boot2docker.js rename to src/Boot2Docker.js index 8e64baedc4..5f5d63a41f 100644 --- a/app/boot2docker.js +++ b/src/Boot2Docker.js @@ -84,7 +84,14 @@ var Boot2Docker = { cmdExec([Boot2Docker.command(), 'upgrade'], callback); }, ip: function (callback) { - cmdExec([Boot2Docker.command(), 'ip'], callback); + exec([Boot2Docker.command(), 'ip'], function (stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + console.log(stderr, stdout, code); + callback(null, stdout.trim().replace('\n', '')); + } + }); }, erase: function (callback) { var VMFileLocation = path.join(homeDir(), 'VirtualBox\\ VMs/boot2docker-vm'); diff --git a/app/ContainerDetails.react.js b/src/ContainerDetails.react.js similarity index 97% rename from app/ContainerDetails.react.js rename to src/ContainerDetails.react.js index 7c05bd677e..63b5d0f3ed 100644 --- a/app/ContainerDetails.react.js +++ b/src/ContainerDetails.react.js @@ -9,8 +9,8 @@ var remote = require('remote'); var dialog = remote.require('dialog'); var ContainerStore = require('./ContainerStore'); var ContainerUtil = require('./ContainerUtil'); -var docker = require('./docker'); -var boot2docker = require('./boot2docker'); +var docker = require('./Docker'); +var boot2docker = require('./Boot2Docker'); var ProgressBar = require('react-bootstrap/ProgressBar'); var Popover = require('react-bootstrap/Popover'); @@ -37,8 +37,6 @@ var ContainerDetails = React.createClass({ componentWillReceiveProps: function () { this.init(); }, - componentWillMount: function () { - }, componentDidMount: function () { this.init(); ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress); @@ -80,7 +78,7 @@ var ContainerDetails = React.createClass({ var $viewPopover = $(this.getDOMNode()).find('.popover-view'); var $volumePopover = $(this.getDOMNode()).find('.popover-volume'); - /*if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) { + /*if ($viewDropdown.offset() && $volumeDropdown.offset()) { $viewPopover.offset({ top: $viewDropdown.offset().top + 32, left: $viewDropdown.offset().left - ($viewPopover.outerWidth() / 2) + 14 @@ -98,16 +96,19 @@ var ContainerDetails = React.createClass({ return; } this.setState({ + progress: ContainerStore.progress(this.getParams().name), env: ContainerUtil.env(container), }); var ports = ContainerUtil.ports(container); var webPorts = ['80', '8000', '8080', '3000', '5000', '2368']; + console.log(ports); this.setState({ ports: ports, defaultPort: _.find(_.keys(ports), function (port) { return webPorts.indexOf(port) !== -1; }) }); + console.log(this.state); this.updateLogs(); }, updateLogs: function (name) { @@ -149,6 +150,7 @@ var ContainerDetails = React.createClass({ console.log('CLICKED'); if (this.state.defaultPort) { console.log(this.state.defaultPort); + console.log(this.state.ports[this.state.defaultPort].url); exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { if (err) { throw err; } }); @@ -322,6 +324,13 @@ var ContainerDetails = React.createClass({ disabled: !this.props.container.State.Running }); + var restartButtonClass = React.addons.classSet({ + btn: true, + 'btn-action': true, + 'with-icon': true, + disabled: this.props.container.State.Restarting + }); + var viewButtonClass = React.addons.classSet({ btn: true, 'btn-action': true, diff --git a/app/ContainerList.react.js b/src/ContainerList.react.js similarity index 92% rename from app/ContainerList.react.js rename to src/ContainerList.react.js index 5218ae4328..ab47065d6e 100644 --- a/app/ContainerList.react.js +++ b/src/ContainerList.react.js @@ -9,12 +9,13 @@ var ModalTrigger = require('react-bootstrap/ModalTrigger'); var ContainerModal = require('./ContainerModal.react'); var ContainerListItem = require('./ContainerListItem.react'); var Header = require('./Header.react'); -var docker = require('./docker'); +var docker = require('./Docker'); var ContainerList = React.createClass({ render: function () { var self = this; var containers = this.props.containers.map(function (container) { + console.log(container); return ( ); diff --git a/app/ContainerListItem.react.js b/src/ContainerListItem.react.js similarity index 100% rename from app/ContainerListItem.react.js rename to src/ContainerListItem.react.js diff --git a/app/ContainerModal.react.js b/src/ContainerModal.react.js similarity index 99% rename from app/ContainerModal.react.js rename to src/ContainerModal.react.js index b9d862a7dd..64543d6f5f 100644 --- a/app/ContainerModal.react.js +++ b/src/ContainerModal.react.js @@ -83,6 +83,9 @@ var ContainerModal = React.createClass({ }, handleClick: function (name, event) { ContainerStore.create(name, 'latest', function (err, containerName) { + if (err) { + throw err; + } this.props.onRequestHide(); }.bind(this)); }, diff --git a/app/ContainerStore.js b/src/ContainerStore.js similarity index 96% rename from app/ContainerStore.js rename to src/ContainerStore.js index ff975c4241..82f3eaa4e2 100644 --- a/app/ContainerStore.js +++ b/src/ContainerStore.js @@ -1,14 +1,14 @@ +var $ = require('jquery'); +var _ = require('underscore'); var EventEmitter = require('events').EventEmitter; var async = require('async'); var path = require('path'); var assign = require('object-assign'); var Stream = require('stream'); var Convert = require('ansi-to-html'); -var docker = require('./docker'); -var registry = require('./registry'); +var docker = require('./Docker'); +var registry = require('./Registry'); var ContainerUtil = require('./ContainerUtil'); -var $ = require('jquery'); -var _ = require('underscore'); var convert = new Convert(); @@ -18,7 +18,6 @@ var _progress = {}; var _logs = {}; var _streams = {}; var _muted = {}; -var _config = {}; var ContainerStore = assign(EventEmitter.prototype, { CLIENT_CONTAINER_EVENT: 'client_container', @@ -226,14 +225,14 @@ var ContainerStore = assign(EventEmitter.prototype, { // If the event is delete, remove the container if (data.status === 'destroy') { var container = _.findWhere(_.values(_containers), {Id: data.id}); - if (!container) { - return; + if (container) { + delete _containers[container.Name]; + if (!_muted[container.Name]) { + this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status); + } + } else { + this.emit(this.SERVER_CONTAINER_EVENT, data.status); } - delete _containers[container.Name]; - if (_muted[container.Name]) { - return; - } - this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status); } else { this.fetchContainer(data.id, function (err) { if (err) { @@ -250,7 +249,12 @@ var ContainerStore = assign(EventEmitter.prototype, { init: function (callback) { // TODO: Load cached data from db on loading this.fetchAllContainers(function (err) { - callback(); + if (err) { + callback(err); + return; + } else { + callback(); + } this.emit(this.CLIENT_CONTAINER_EVENT); this._resumePulling(); this._startListeningToEvents(); @@ -366,7 +370,6 @@ var ContainerStore = assign(EventEmitter.prototype, { }); stream.on('end', function () { delete _streams[name]; - console.log('end', name); }); }); }, @@ -381,6 +384,10 @@ var ContainerStore = assign(EventEmitter.prototype, { if (!data) { // Pull image self._createPlaceholderContainer(imageName, containerName, function (err, container) { + if (err) { + callback(err); + return; + } _containers[containerName] = container; self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create'); _muted[containerName] = true; diff --git a/app/ContainerUtil.js b/src/ContainerUtil.js similarity index 93% rename from app/ContainerUtil.js rename to src/ContainerUtil.js index 3707a41211..61159f7186 100644 --- a/app/ContainerUtil.js +++ b/src/ContainerUtil.js @@ -1,5 +1,5 @@ var _ = require('underscore'); -var docker = require('./docker'); +var docker = require('./Docker'); var ContainerUtil = { env: function (container) { @@ -15,7 +15,6 @@ var ContainerUtil = { ports: function (container, callback) { var res = {}; var ip = docker.host; - console.log(container); _.each(container.NetworkSettings.Ports, function (value, key) { var dockerPort = key.split('/')[0]; var localUrl = null; diff --git a/app/Containers.react.js b/src/Containers.react.js similarity index 98% rename from app/Containers.react.js rename to src/Containers.react.js index 3377f4fd8a..58174829b5 100644 --- a/app/Containers.react.js +++ b/src/Containers.react.js @@ -1,3 +1,6 @@ +var async = require('async'); +var _ = require('underscore'); +var $ = require('jquery'); var React = require('react/addons'); var Router = require('react-router'); var RetinaImage = require('react-retina-image'); @@ -6,11 +9,7 @@ var ContainerModal = require('./ContainerModal.react'); var ContainerStore = require('./ContainerStore'); var ContainerList = require('./ContainerList.react'); var Header = require('./Header.react'); -var async = require('async'); -var _ = require('underscore'); -var docker = require('./docker'); -var $ = require('jquery'); - +var docker = require('./Docker'); var Containers = React.createClass({ mixins: [Router.Navigation, Router.State], getInitialState: function () { diff --git a/app/docker.js b/src/Docker.js similarity index 93% rename from app/docker.js rename to src/Docker.js index ac9701546a..6c8768bf2e 100644 --- a/app/docker.js +++ b/src/Docker.js @@ -12,8 +12,7 @@ var Docker = { return; } this._client = new dockerode({ - protocol: 'https', - host: this.host, + host: '192.168.59.103', port: 2376, ca: fs.readFileSync(path.join(certDir, 'ca.pem')), cert: fs.readFileSync(path.join(certDir, 'cert.pem')), diff --git a/app/Header.react.js b/src/Header.react.js similarity index 100% rename from app/Header.react.js rename to src/Header.react.js diff --git a/app/main.js b/src/Main.js similarity index 66% rename from app/main.js rename to src/Main.js index 5970a0a0b1..e58c34486a 100644 --- a/app/main.js +++ b/src/Main.js @@ -1,33 +1,23 @@ -var module = require('module'); -require.main.paths.splice(0, 0, process.env.NODE_PATH); - -var Bugsnag = require('bugsnag-js'); var React = require('react'); var Router = require('react-router'); var RetinaImage = require('react-retina-image'); var async = require('async'); -var docker = require('./docker'); +var docker = require('./Docker'); var router = require('./router'); var boot2docker = require('./boot2docker'); var ContainerStore = require('./ContainerStore'); +var SetupStore = require('./ContainerStore'); var Menu = require('./Menu'); var remote = require('remote'); var app = remote.require('app'); var ipc = require('ipc'); - var Route = Router.Route; var NotFoundRoute = Router.NotFoundRoute; var DefaultRoute = Router.DefaultRoute; var Link = Router.Link; var RouteHandler = Router.RouteHandler; -Bugsnag.apiKey = 'fc51aab02ce9dd1bb6ebc9fe2f4d43d7'; -Bugsnag.autoNotify = true; -Bugsnag.releaseStage = process.env.NODE_ENV === 'development' ? 'development' : 'production'; -Bugsnag.notifyReleaseStages = []; -Bugsnag.appVersion = app.getVersion(); - if (process.env.NODE_ENV === 'development') { var script = document.createElement('script'); script.type = 'text/javascript'; @@ -37,13 +27,25 @@ if (process.env.NODE_ENV === 'development') { } if (!window.location.hash.length || window.location.hash === '#/') { - router.run(function (Handler) { - React.render(, document.body); + SetupStore.run(function (err) { + boot2docker.ip(function (err, ip) { + if (err) console.log(err); + docker.setHost(ip); + router.transitionTo('containers'); + ContainerStore.init(function (err) { + if (err) console.log(err); + router.run(function (Handler) { + React.render(, document.body); + }); + }); + }); }); } else { boot2docker.ip(function (err, ip) { + if (err) console.log(err); docker.setHost(ip); - ContainerStore.init(function () { + ContainerStore.init(function (err) { + if (err) console.log(err); router.run(function (Handler) { React.render(, document.body); }); diff --git a/app/Menu.js b/src/Menu.js similarity index 98% rename from app/Menu.js rename to src/Menu.js index 68bcd9cf2a..336209f99f 100644 --- a/app/Menu.js +++ b/src/Menu.js @@ -3,7 +3,7 @@ var app = remote.require('app'); var Menu = remote.require('menu'); var MenuItem = remote.require('menu-item'); var BrowserWindow = remote.require('browser-window'); -var router = require('./router'); +var router = require('./Router'); // main.js var template = [ diff --git a/app/NoContainers.react.js b/src/NoContainers.react.js similarity index 100% rename from app/NoContainers.react.js rename to src/NoContainers.react.js diff --git a/app/Preferences.react.js b/src/Preferences.react.js similarity index 100% rename from app/Preferences.react.js rename to src/Preferences.react.js diff --git a/app/Radial.react.js b/src/Radial.react.js similarity index 100% rename from app/Radial.react.js rename to src/Radial.react.js diff --git a/app/registry.js b/src/Registry.js similarity index 100% rename from app/registry.js rename to src/Registry.js diff --git a/app/router.js b/src/Router.js similarity index 77% rename from app/router.js rename to src/Router.js index d419f62176..b02ac78139 100644 --- a/app/router.js +++ b/src/Router.js @@ -1,5 +1,5 @@ var Router = require('react-router'); -var routes = require('./routes'); +var routes = require('./Routes'); var router = Router.create({ routes: routes diff --git a/app/routes.js b/src/Routes.js similarity index 100% rename from app/routes.js rename to src/Routes.js diff --git a/src/Setup.react.js b/src/Setup.react.js new file mode 100644 index 0000000000..c86f389ed4 --- /dev/null +++ b/src/Setup.react.js @@ -0,0 +1,55 @@ +var React = require('react/addons'); +var Router = require('react-router'); +var Radial = require('./Radial.react.js'); +var async = require('async'); +var assign = require('object-assign'); +var fs = require('fs'); +var path = require('path'); +var virtualbox = require('./Virtualbox'); +var util = require('./Util'); +var SetupStore = require('./SetupStore'); + +var Setup = React.createClass({ + mixins: [ Router.Navigation ], + getInitialState: function () { + return { + message: '', + progress: 0 + }; + }, + componentWillMount: function () { + SetupStore.on(SetupStore.PROGRESS_EVENT, this.update); + }, + componentDidMount: function () { + }, + update: function () { + + }, + render: function () { + var radial; + if (this.state.progress) { + radial = ; + } else if (this.state.error) { + radial = ; + } else { + radial = ; + } + if (this.state.error) { + return ( +
+ {radial} +

Error: {this.state.error}

+
+ ); + } else { + return ( +
+ {radial} +

{this.state.message}

+
+ ); + } + } +}); + +module.exports = Setup; diff --git a/src/SetupStore.js b/src/SetupStore.js new file mode 100644 index 0000000000..3dcf5fbdb1 --- /dev/null +++ b/src/SetupStore.js @@ -0,0 +1,201 @@ +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); +var async = require('async'); +var fs = require('fs'); +var path = require('path'); +var exec = require('exec'); +var boot2docker = require('./Boot2Docker'); +var virtualbox = require('./Virtualbox'); +var setupUtil = require('./SetupUtil'); +var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); + +var _currentStep = null; +var _error = null; +var _progress = null; + +var SetupStore = assign(EventEmitter.prototype, { + PROGRESS_EVENT: 'setup_progress', + STEP_EVENT: 'setup_step', + ERROR_EVENT: 'setup_error', + downloadVirtualboxStep: { + _download: function (callback, progressCallback) { + setupUtil.virtualboxSHA256(packagejson['virtualbox-version'], packagejson['virtualbox-filename'], function (err, checksum) { + if (err) { + callback(err); + return; + } + var url = 'http://download.virtualbox.org/virtualbox/' + packagejson['virtualbox-version'] + '/' + packagejson['virtualbox-filename']; + var downloadPath = path.join(setupUtil.supportDir(), packagejson['virtualbox-filename']); + setupUtil.download(url, downloadPath, checksum, function (err) { + callback(err); + }, function (progress) { + progressCallback(progress); + }); + }); + }, + run: function (callback, progressCallback) { + if (virtualbox.installed()) { + virtualbox.version(function (err, version) { + if (err) {callback(err); return;} + if (setupUtil.compareVersions(version, packagejson['virtualbox-required-version']) < 0) { + this._download(callback, progressCallback); + } else { + callback(); + } + }); + } else { + this._download(callback, progressCallback); + } + }, + name: 'downloading_virtualbox', + message: 'Downloading Virtualbox', + }, + installVirtualboxStep: { + _install: function (callback) { + exec(['hdiutil', 'attach', path.join(setupUtil.supportDir(), 'VirtualBox-4.3.20-96996-OSX.dmg')], function (stderr, stdout, code) { + if (code) { + callback(stderr); + return; + } + var iconPath = path.join(setupUtil.resourceDir(), 'kitematic.icns'); + setupUtil.isSudo(function (err, isSudo) { + sudoCmd = isSudo ? ['sudo'] : [path.join(setupUtil.resourceDir(), 'cocoasudo'), '--icon=' + iconPath, '--prompt=Kitematic requires administrative privileges to install VirtualBox and copy itself to the Applications folder.']; + sudoCmd.push.apply(sudoCmd, ['installer', '-pkg', '/Volumes/VirtualBox/VirtualBox.pkg', '-target', '/']); + exec(sudoCmd, function (stderr, stdout, code) { + if (code) { + console.log(stderr); + console.log(stdout); + callback('Could not install virtualbox.'); + } else { + exec(['hdiutil', 'detach', '/Volumes/VirtualBox'], function(stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + callback(); + } + }); + } + }); + }); + }); + }, + run: function (callback) { + var self = this; + if (virtualbox.installed()) { + virtualbox.version(function (err, version) { + if (setupUtil.compareVersions(version, packagejson['virtualbox-required-version']) < 0) { + virtualbox.kill(function (err) { + if (err) {callback(err); return;} + self._install(function(err) { + callback(err); + }); + }); + } else { + callback(); + } + }); + } else { + self._install(function(err) { + callback(err); + }); + } + }, + name: 'installing_virtualbox', + message: 'Installing VirtualBox', + }, + cleanupKitematicStep: { + run: function (callback) { + virtualbox.vmdestroy('kitematic-vm', function (err, removed) { + if (err) { + console.log(err); + } + callback(); + }); + }, + name: 'cleanup_kitematic', + message: 'Cleaning up existing Kitematic install...' + }, + initBoot2DockerStep: { + run: function (callback) { + boot2docker.exists(function (err, exists) { + if (err) { callback(err); return; } + if (!exists) { + boot2docker.init(function (err) { + callback(err); + }); + } else { + if (!boot2docker.sshKeyExists()) { + callback('Boot2Docker SSH key doesn\'t exist. Fix by removing the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); + } else { + boot2docker.isoVersion(function (err, version) { + if (err || setupUtil.compareVersions(version, boot2docker.version()) < 0) { + boot2docker.stop(function(err) { + boot2docker.upgrade(function (err) { + callback(err); + }); + }); + } else { + callback(); + } + }); + } + } + }); + }, + name: 'init_boot2docker', + message: 'Setting up the Docker VM...' + }, + startBoot2DockerStep: { + run: function (callback) { + boot2docker.waitWhileStatus('saving', function (err) { + boot2docker.status(function (err, status) { + if (err) {callback(err); return;} + if (status !== 'running') { + boot2docker.start(function (err) { + callback(err); + }); + } else { + callback(); + } + }); + }); + }, + name: 'start_boot2docker', + message: 'Starting the Docker VM...' + }, + step: function () { + return _currentStep; + }, + progress: function () { + return _progress; + }, + run: function (callback) { + var self = this; + var steps = [this.downloadVirtualboxStep, this.installVirtualboxStep, this.cleanupKitematicStep, this.initBoot2DockerStep, this.startBoot2DockerStep]; + async.eachSeries(steps, function (step, callback) { + console.log(step.name); + _currentStep = step; + _progress = null; + step.run(function (err) { + if (err) { + callback(err); + } else { + self.emit(self.STEP_EVENT); + callback(); + } + }, function (progress) { + self.emit(self.PROGRESS_EVENT, progress); + _progress = progress; + }); + }, function (err) { + if (err) { + self.emit(self.ERROR_EVENT, _error); + callback(err); + } else { + callback(); + } + }); + } +}); + +module.exports = SetupStore; diff --git a/app/util.js b/src/SetupUtil.js similarity index 66% rename from app/util.js rename to src/SetupUtil.js index 78a4c61035..d3b8d696e9 100644 --- a/app/util.js +++ b/src/SetupUtil.js @@ -1,13 +1,13 @@ -var path = require('path'); -var fs = require('fs'); -var nodeCrypto = require('crypto'); var request = require('request'); var progress = require('request-progress'); +var path = require('path'); +var crypto = require('crypto'); +var fs = require('fs'); var exec = require('exec'); -var Util = { +var SetupUtil = { supportDir: function (callback) { - var dirs = ['Application\ Support', 'Kitematic']; + var dirs = ['Library', 'Application\ Support', 'Kitematic']; var acc = process.env.HOME; dirs.forEach(function (d) { acc = path.join(acc, d); @@ -17,6 +17,19 @@ var Util = { }); return acc; }, + resourceDir: function (callback) { + return process.env.RESOURCES_PATH; + }, + isSudo: function (callback) { + exec(['sudo', '-n', '-u', 'root', 'true'], function (stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + var isSudo = stderr.indexOf('a password is required') === -1; + callback(null, isSudo); + } + }); + }, download: function (url, filename, checksum, callback, progressCallback) { var doDownload = function () { progress(request({ @@ -37,8 +50,7 @@ var Util = { // Compare checksum to see if it already exists first if (fs.existsSync(filename)) { - var existingChecksum = nodeCrypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); - console.log(existingChecksum); + var existingChecksum = crypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); if (existingChecksum !== checksum) { fs.unlinkSync(filename); doDownload(); @@ -49,6 +61,22 @@ var Util = { doDownload(); } }, + virtualboxSHA256: function (version, filename, callback) { + var checksumUrl = 'http://dlc-cdn.sun.com/virtualbox/' + version + '/SHA256SUMS'; + request(checksumUrl, function (error, response, body) { + if (error) { + callback(error); + return; + } + var checksums = body.split('\n').map(function (line) { + return line.split(' *'); + }).reduce(function (obj, pair) { + obj[pair[1]] = pair[0]; + return obj; + }, {}); + callback(null, checksums[filename]); + }); + }, compareVersions: function (v1, v2, options) { var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, @@ -100,4 +128,4 @@ var Util = { } }; -module.exports = Util; +module.exports = SetupUtil; diff --git a/app/virtualbox.js b/src/Virtualbox.js similarity index 64% rename from app/virtualbox.js rename to src/Virtualbox.js index a7192793de..446537c461 100644 --- a/app/virtualbox.js +++ b/src/Virtualbox.js @@ -2,35 +2,21 @@ var fs = require('fs'); var exec = require('exec'); var path = require('path'); var async = require('async'); -var util = require('./util'); +var util = require('./Util'); var VirtualBox = { - REQUIRED_VERSION: '4.3.18', - INCLUDED_VERSION: '4.3.18', - INSTALLER_FILENAME: 'virtualbox-4.3.18.pkg', - INSTALLER_CHECKSUM: '5836c94481c460c648b9216386591a2915293ac86b9bb6c57746637796af6af2', command: function () { return '/usr/bin/VBoxManage'; }, installed: function () { return fs.existsSync('/usr/bin/VBoxManage') && fs.existsSync('/Applications/VirtualBox.app/Contents/MacOS/VirtualBox'); }, - install: function (callback) { - // -W waits for the process to close before finishing. - exec('open -W ' + path.join(util.supportDir(), this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) { - if (code) { - callback(stderr); - return; - } - callback(null); - }); - }, version: function (callback) { if (!this.installed()) { callback('VirtualBox not installed.'); return; } - exec('/usr/bin/VBoxManage -v', function (stderr, stdout, code) { + exec([this.command(), '-v'], function (stderr, stdout, code) { if (code) { callback(stderr); return; @@ -44,12 +30,12 @@ var VirtualBox = { callback(null, match[1]); }); }, - saveVMs: function (callback) { + poweroff: function (callback) { if (!this.installed()) { callback('VirtualBox not installed.'); return; } - exec('list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} VBoxManage controlvm {} savestate', function (stderr, stdout, code) { + exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} acpipowerbutton', function (stderr, stdout, code) { if (code) { callback(stderr); } else { @@ -58,7 +44,7 @@ var VirtualBox = { }); }, kill: function (callback) { - this.saveRunningVMs(function (err) { + this.poweroff(function (err) { if (err) {callback(err); return;} exec('pkill VirtualBox', function (stderr, stdout, code) { if (code) {callback(stderr); return;} @@ -69,7 +55,7 @@ var VirtualBox = { }); }); }, - vmState: function (name, callback) { + vmstate: function (name, callback) { exec(this.command() + ' showvminfo ' + name + ' --machinereadable', function (stderr, stdout, code) { if (code) { callback(stderr); return; } var match = stdout.match(/VMState="(\w+)"/); @@ -80,24 +66,25 @@ var VirtualBox = { callback(null, match[1]); }); }, - deleteVM:function (name, callback) { - VirtualBox.vmState(name, function (err, state) { + vmdestroy: function (name, callback) { + var self = this; + this.vmstate(name, function (err, state) { // No VM found if (err) { callback(null, false); return; } - VirtualBox.exec('controlvm ' + name + ' acpipowerbutton', function (stderr, stdout, code) { + exec('/usr/bin/VBoxManage controlvm ' + name + ' acpipowerbutton', function (stderr, stdout, code) { if (code) { callback(stderr, false); return; } var state = null; async.until(function () { return state === 'poweroff'; }, function (callback) { - VirtualBox.vmState(name, function (err, newState) { + self.vmstate(name, function (err, newState) { if (err) { callback(err); return; } state = newState; setTimeout(callback, 250); }); }, function (err) { - VirtualBox.exec('unregistervm ' + name + ' --delete', function (stderr, stdout, code) { + exec('/usr/bin/VBoxManage unregistervm ' + name + ' --delete', function (stderr, stdout, code) { if (code) { callback(err); return; } callback(); }); diff --git a/app/styles/bootstrap/alerts.less b/styles/bootstrap/alerts.less similarity index 100% rename from app/styles/bootstrap/alerts.less rename to styles/bootstrap/alerts.less diff --git a/app/styles/bootstrap/badges.less b/styles/bootstrap/badges.less similarity index 100% rename from app/styles/bootstrap/badges.less rename to styles/bootstrap/badges.less diff --git a/app/styles/bootstrap/bootstrap.less b/styles/bootstrap/bootstrap.less similarity index 100% rename from app/styles/bootstrap/bootstrap.less rename to styles/bootstrap/bootstrap.less diff --git a/app/styles/bootstrap/breadcrumbs.less b/styles/bootstrap/breadcrumbs.less similarity index 100% rename from app/styles/bootstrap/breadcrumbs.less rename to styles/bootstrap/breadcrumbs.less diff --git a/app/styles/bootstrap/button-groups.less b/styles/bootstrap/button-groups.less similarity index 100% rename from app/styles/bootstrap/button-groups.less rename to styles/bootstrap/button-groups.less diff --git a/app/styles/bootstrap/buttons.less b/styles/bootstrap/buttons.less similarity index 100% rename from app/styles/bootstrap/buttons.less rename to styles/bootstrap/buttons.less diff --git a/app/styles/bootstrap/carousel.less b/styles/bootstrap/carousel.less similarity index 100% rename from app/styles/bootstrap/carousel.less rename to styles/bootstrap/carousel.less diff --git a/app/styles/bootstrap/close.less b/styles/bootstrap/close.less similarity index 100% rename from app/styles/bootstrap/close.less rename to styles/bootstrap/close.less diff --git a/app/styles/bootstrap/code.less b/styles/bootstrap/code.less similarity index 100% rename from app/styles/bootstrap/code.less rename to styles/bootstrap/code.less diff --git a/app/styles/bootstrap/component-animations.less b/styles/bootstrap/component-animations.less similarity index 100% rename from app/styles/bootstrap/component-animations.less rename to styles/bootstrap/component-animations.less diff --git a/app/styles/bootstrap/dropdowns.less b/styles/bootstrap/dropdowns.less similarity index 100% rename from app/styles/bootstrap/dropdowns.less rename to styles/bootstrap/dropdowns.less diff --git a/app/styles/bootstrap/forms.less b/styles/bootstrap/forms.less similarity index 100% rename from app/styles/bootstrap/forms.less rename to styles/bootstrap/forms.less diff --git a/app/styles/bootstrap/glyphicons.less b/styles/bootstrap/glyphicons.less similarity index 100% rename from app/styles/bootstrap/glyphicons.less rename to styles/bootstrap/glyphicons.less diff --git a/app/styles/bootstrap/grid.less b/styles/bootstrap/grid.less similarity index 100% rename from app/styles/bootstrap/grid.less rename to styles/bootstrap/grid.less diff --git a/app/styles/bootstrap/input-groups.less b/styles/bootstrap/input-groups.less similarity index 100% rename from app/styles/bootstrap/input-groups.less rename to styles/bootstrap/input-groups.less diff --git a/app/styles/bootstrap/jumbotron.less b/styles/bootstrap/jumbotron.less similarity index 100% rename from app/styles/bootstrap/jumbotron.less rename to styles/bootstrap/jumbotron.less diff --git a/app/styles/bootstrap/labels.less b/styles/bootstrap/labels.less similarity index 100% rename from app/styles/bootstrap/labels.less rename to styles/bootstrap/labels.less diff --git a/app/styles/bootstrap/list-group.less b/styles/bootstrap/list-group.less similarity index 100% rename from app/styles/bootstrap/list-group.less rename to styles/bootstrap/list-group.less diff --git a/app/styles/bootstrap/media.less b/styles/bootstrap/media.less similarity index 100% rename from app/styles/bootstrap/media.less rename to styles/bootstrap/media.less diff --git a/app/styles/bootstrap/mixins.less b/styles/bootstrap/mixins.less similarity index 100% rename from app/styles/bootstrap/mixins.less rename to styles/bootstrap/mixins.less diff --git a/app/styles/bootstrap/mixins/alerts.less b/styles/bootstrap/mixins/alerts.less similarity index 100% rename from app/styles/bootstrap/mixins/alerts.less rename to styles/bootstrap/mixins/alerts.less diff --git a/app/styles/bootstrap/mixins/background-variant.less b/styles/bootstrap/mixins/background-variant.less similarity index 100% rename from app/styles/bootstrap/mixins/background-variant.less rename to styles/bootstrap/mixins/background-variant.less diff --git a/app/styles/bootstrap/mixins/border-radius.less b/styles/bootstrap/mixins/border-radius.less similarity index 100% rename from app/styles/bootstrap/mixins/border-radius.less rename to styles/bootstrap/mixins/border-radius.less diff --git a/app/styles/bootstrap/mixins/buttons.less b/styles/bootstrap/mixins/buttons.less similarity index 100% rename from app/styles/bootstrap/mixins/buttons.less rename to styles/bootstrap/mixins/buttons.less diff --git a/app/styles/bootstrap/mixins/center-block.less b/styles/bootstrap/mixins/center-block.less similarity index 100% rename from app/styles/bootstrap/mixins/center-block.less rename to styles/bootstrap/mixins/center-block.less diff --git a/app/styles/bootstrap/mixins/clearfix.less b/styles/bootstrap/mixins/clearfix.less similarity index 100% rename from app/styles/bootstrap/mixins/clearfix.less rename to styles/bootstrap/mixins/clearfix.less diff --git a/app/styles/bootstrap/mixins/forms.less b/styles/bootstrap/mixins/forms.less similarity index 100% rename from app/styles/bootstrap/mixins/forms.less rename to styles/bootstrap/mixins/forms.less diff --git a/app/styles/bootstrap/mixins/gradients.less b/styles/bootstrap/mixins/gradients.less similarity index 100% rename from app/styles/bootstrap/mixins/gradients.less rename to styles/bootstrap/mixins/gradients.less diff --git a/app/styles/bootstrap/mixins/grid-framework.less b/styles/bootstrap/mixins/grid-framework.less similarity index 100% rename from app/styles/bootstrap/mixins/grid-framework.less rename to styles/bootstrap/mixins/grid-framework.less diff --git a/app/styles/bootstrap/mixins/grid.less b/styles/bootstrap/mixins/grid.less similarity index 100% rename from app/styles/bootstrap/mixins/grid.less rename to styles/bootstrap/mixins/grid.less diff --git a/app/styles/bootstrap/mixins/hide-text.less b/styles/bootstrap/mixins/hide-text.less similarity index 100% rename from app/styles/bootstrap/mixins/hide-text.less rename to styles/bootstrap/mixins/hide-text.less diff --git a/app/styles/bootstrap/mixins/image.less b/styles/bootstrap/mixins/image.less similarity index 100% rename from app/styles/bootstrap/mixins/image.less rename to styles/bootstrap/mixins/image.less diff --git a/app/styles/bootstrap/mixins/labels.less b/styles/bootstrap/mixins/labels.less similarity index 100% rename from app/styles/bootstrap/mixins/labels.less rename to styles/bootstrap/mixins/labels.less diff --git a/app/styles/bootstrap/mixins/list-group.less b/styles/bootstrap/mixins/list-group.less similarity index 100% rename from app/styles/bootstrap/mixins/list-group.less rename to styles/bootstrap/mixins/list-group.less diff --git a/app/styles/bootstrap/mixins/nav-divider.less b/styles/bootstrap/mixins/nav-divider.less similarity index 100% rename from app/styles/bootstrap/mixins/nav-divider.less rename to styles/bootstrap/mixins/nav-divider.less diff --git a/app/styles/bootstrap/mixins/nav-vertical-align.less b/styles/bootstrap/mixins/nav-vertical-align.less similarity index 100% rename from app/styles/bootstrap/mixins/nav-vertical-align.less rename to styles/bootstrap/mixins/nav-vertical-align.less diff --git a/app/styles/bootstrap/mixins/opacity.less b/styles/bootstrap/mixins/opacity.less similarity index 100% rename from app/styles/bootstrap/mixins/opacity.less rename to styles/bootstrap/mixins/opacity.less diff --git a/app/styles/bootstrap/mixins/pagination.less b/styles/bootstrap/mixins/pagination.less similarity index 100% rename from app/styles/bootstrap/mixins/pagination.less rename to styles/bootstrap/mixins/pagination.less diff --git a/app/styles/bootstrap/mixins/panels.less b/styles/bootstrap/mixins/panels.less similarity index 100% rename from app/styles/bootstrap/mixins/panels.less rename to styles/bootstrap/mixins/panels.less diff --git a/app/styles/bootstrap/mixins/progress-bar.less b/styles/bootstrap/mixins/progress-bar.less similarity index 100% rename from app/styles/bootstrap/mixins/progress-bar.less rename to styles/bootstrap/mixins/progress-bar.less diff --git a/app/styles/bootstrap/mixins/reset-filter.less b/styles/bootstrap/mixins/reset-filter.less similarity index 100% rename from app/styles/bootstrap/mixins/reset-filter.less rename to styles/bootstrap/mixins/reset-filter.less diff --git a/app/styles/bootstrap/mixins/resize.less b/styles/bootstrap/mixins/resize.less similarity index 100% rename from app/styles/bootstrap/mixins/resize.less rename to styles/bootstrap/mixins/resize.less diff --git a/app/styles/bootstrap/mixins/responsive-visibility.less b/styles/bootstrap/mixins/responsive-visibility.less similarity index 100% rename from app/styles/bootstrap/mixins/responsive-visibility.less rename to styles/bootstrap/mixins/responsive-visibility.less diff --git a/app/styles/bootstrap/mixins/size.less b/styles/bootstrap/mixins/size.less similarity index 100% rename from app/styles/bootstrap/mixins/size.less rename to styles/bootstrap/mixins/size.less diff --git a/app/styles/bootstrap/mixins/tab-focus.less b/styles/bootstrap/mixins/tab-focus.less similarity index 100% rename from app/styles/bootstrap/mixins/tab-focus.less rename to styles/bootstrap/mixins/tab-focus.less diff --git a/app/styles/bootstrap/mixins/table-row.less b/styles/bootstrap/mixins/table-row.less similarity index 100% rename from app/styles/bootstrap/mixins/table-row.less rename to styles/bootstrap/mixins/table-row.less diff --git a/app/styles/bootstrap/mixins/text-emphasis.less b/styles/bootstrap/mixins/text-emphasis.less similarity index 100% rename from app/styles/bootstrap/mixins/text-emphasis.less rename to styles/bootstrap/mixins/text-emphasis.less diff --git a/app/styles/bootstrap/mixins/text-overflow.less b/styles/bootstrap/mixins/text-overflow.less similarity index 100% rename from app/styles/bootstrap/mixins/text-overflow.less rename to styles/bootstrap/mixins/text-overflow.less diff --git a/app/styles/bootstrap/mixins/vendor-prefixes.less b/styles/bootstrap/mixins/vendor-prefixes.less similarity index 100% rename from app/styles/bootstrap/mixins/vendor-prefixes.less rename to styles/bootstrap/mixins/vendor-prefixes.less diff --git a/app/styles/bootstrap/modals.less b/styles/bootstrap/modals.less similarity index 100% rename from app/styles/bootstrap/modals.less rename to styles/bootstrap/modals.less diff --git a/app/styles/bootstrap/navbar.less b/styles/bootstrap/navbar.less similarity index 100% rename from app/styles/bootstrap/navbar.less rename to styles/bootstrap/navbar.less diff --git a/app/styles/bootstrap/navs.less b/styles/bootstrap/navs.less similarity index 100% rename from app/styles/bootstrap/navs.less rename to styles/bootstrap/navs.less diff --git a/app/styles/bootstrap/normalize.less b/styles/bootstrap/normalize.less similarity index 100% rename from app/styles/bootstrap/normalize.less rename to styles/bootstrap/normalize.less diff --git a/app/styles/bootstrap/pager.less b/styles/bootstrap/pager.less similarity index 100% rename from app/styles/bootstrap/pager.less rename to styles/bootstrap/pager.less diff --git a/app/styles/bootstrap/pagination.less b/styles/bootstrap/pagination.less similarity index 100% rename from app/styles/bootstrap/pagination.less rename to styles/bootstrap/pagination.less diff --git a/app/styles/bootstrap/panels.less b/styles/bootstrap/panels.less similarity index 100% rename from app/styles/bootstrap/panels.less rename to styles/bootstrap/panels.less diff --git a/app/styles/bootstrap/popovers.less b/styles/bootstrap/popovers.less similarity index 100% rename from app/styles/bootstrap/popovers.less rename to styles/bootstrap/popovers.less diff --git a/app/styles/bootstrap/print.less b/styles/bootstrap/print.less similarity index 100% rename from app/styles/bootstrap/print.less rename to styles/bootstrap/print.less diff --git a/app/styles/bootstrap/progress-bars.less b/styles/bootstrap/progress-bars.less similarity index 100% rename from app/styles/bootstrap/progress-bars.less rename to styles/bootstrap/progress-bars.less diff --git a/app/styles/bootstrap/responsive-embed.less b/styles/bootstrap/responsive-embed.less similarity index 100% rename from app/styles/bootstrap/responsive-embed.less rename to styles/bootstrap/responsive-embed.less diff --git a/app/styles/bootstrap/responsive-utilities.less b/styles/bootstrap/responsive-utilities.less similarity index 100% rename from app/styles/bootstrap/responsive-utilities.less rename to styles/bootstrap/responsive-utilities.less diff --git a/app/styles/bootstrap/scaffolding.less b/styles/bootstrap/scaffolding.less similarity index 100% rename from app/styles/bootstrap/scaffolding.less rename to styles/bootstrap/scaffolding.less diff --git a/app/styles/bootstrap/tables.less b/styles/bootstrap/tables.less similarity index 100% rename from app/styles/bootstrap/tables.less rename to styles/bootstrap/tables.less diff --git a/app/styles/bootstrap/theme.less b/styles/bootstrap/theme.less similarity index 100% rename from app/styles/bootstrap/theme.less rename to styles/bootstrap/theme.less diff --git a/app/styles/bootstrap/thumbnails.less b/styles/bootstrap/thumbnails.less similarity index 100% rename from app/styles/bootstrap/thumbnails.less rename to styles/bootstrap/thumbnails.less diff --git a/app/styles/bootstrap/tooltip.less b/styles/bootstrap/tooltip.less similarity index 100% rename from app/styles/bootstrap/tooltip.less rename to styles/bootstrap/tooltip.less diff --git a/app/styles/bootstrap/type.less b/styles/bootstrap/type.less similarity index 100% rename from app/styles/bootstrap/type.less rename to styles/bootstrap/type.less diff --git a/app/styles/bootstrap/utilities.less b/styles/bootstrap/utilities.less similarity index 100% rename from app/styles/bootstrap/utilities.less rename to styles/bootstrap/utilities.less diff --git a/app/styles/bootstrap/variables.less b/styles/bootstrap/variables.less similarity index 100% rename from app/styles/bootstrap/variables.less rename to styles/bootstrap/variables.less diff --git a/app/styles/bootstrap/wells.less b/styles/bootstrap/wells.less similarity index 100% rename from app/styles/bootstrap/wells.less rename to styles/bootstrap/wells.less diff --git a/app/styles/clearsans.less b/styles/clearsans.less similarity index 100% rename from app/styles/clearsans.less rename to styles/clearsans.less diff --git a/app/styles/container-modal.less b/styles/container-modal.less similarity index 100% rename from app/styles/container-modal.less rename to styles/container-modal.less diff --git a/app/styles/containers.less b/styles/containers.less similarity index 100% rename from app/styles/containers.less rename to styles/containers.less diff --git a/app/styles/header.less b/styles/header.less similarity index 100% rename from app/styles/header.less rename to styles/header.less diff --git a/app/styles/icons.less b/styles/icons.less similarity index 100% rename from app/styles/icons.less rename to styles/icons.less diff --git a/app/styles/main.less b/styles/main.less similarity index 99% rename from app/styles/main.less rename to styles/main.less index c852cd8fc8..1c23e621c5 100644 --- a/app/styles/main.less +++ b/styles/main.less @@ -62,6 +62,7 @@ html, body { border: 1px solid #ddd; } + @-webkit-keyframes spin { from { -webkit-transform: rotate(0deg); diff --git a/app/styles/preferences.less b/styles/preferences.less similarity index 100% rename from app/styles/preferences.less rename to styles/preferences.less diff --git a/app/styles/radial.less b/styles/radial.less similarity index 100% rename from app/styles/radial.less rename to styles/radial.less diff --git a/app/styles/retina.less b/styles/retina.less similarity index 100% rename from app/styles/retina.less rename to styles/retina.less diff --git a/app/styles/setup.less b/styles/setup.less similarity index 100% rename from app/styles/setup.less rename to styles/setup.less diff --git a/app/styles/theme.less b/styles/theme.less similarity index 100% rename from app/styles/theme.less rename to styles/theme.less diff --git a/app/styles/variables.less b/styles/variables.less similarity index 100% rename from app/styles/variables.less rename to styles/variables.less diff --git a/tests/SetupStore-integration.js b/tests/SetupStore-integration.js new file mode 100644 index 0000000000..2dde66b446 --- /dev/null +++ b/tests/SetupStore-integration.js @@ -0,0 +1,99 @@ +var virtualbox = require('../build/Virtualbox'); +var SetupStore = require('../build/SetupStore'); +var setupUtil = require('../build/SetupUtil'); +var path = require('path'); +var fs = require('fs'); +var child_process = require('child_process'); +var exec = require('exec'); +var rimraf = require('rimraf'); +var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 300000; // 5 minutes + +describe('Setup', function () { + describe('without virtualbox installed or downloaded', function () { + var virtualboxFile = path.join(setupUtil.supportDir(), packagejson['virtualbox-filename']); + beforeEach(function () { + if (fs.existsSync(virtualboxFile)) { + fs.unlinkSync(virtualboxFile); + } + spyOn(virtualbox, 'installed').and.returnValue(false); + }); + + it('downloads virtualbox from the official website', function (done) { + SetupStore.downloadVirtualboxStep.run(function (err) { + expect(err).toBeFalsy(); + expect(fs.existsSync(virtualboxFile)).toBe(true); + done(); + }, function (progress) { + + }); + }); + }); + + describe('with virtualbox downloaded but not installed', function () { + beforeEach(function (done) { + // 5 minute timeout per test + + SetupStore.downloadVirtualboxStep.run(function (err) { + if (virtualbox.installed()) { + virtualbox.kill(function (callback) { + done(); + }); + } else { + done(); + } + }, function (progress) {}); + }); + + it('does install virtualbox', function (done) { + SetupStore.installVirtualboxStep.run(function (err) { + expect(err).toBeFalsy(); + expect(fs.existsSync(virtualbox.command())).toBe(true); + done(); + }); + }); + }); + + describe('with virtualbox installed', function () { + + // Before each teardown the boot2docker VM, keys and anything else + + describe('and with a kitematic vm', function () { + + }); + + describe('and without a boot2docker vm', function () { + + }); + + describe('and with an old boot2docker vm', function () { + + }); + + describe('and with a very old boot2docker vm', function () { + + }); + + describe('and with a boot2docker vm running', function () { + + }); + + describe('and with a boot2docker vm but with no ssh keys', function () { + + }); + + describe('and with a boot2docker vm being powered off', function () { + + }); + + describe('and with a boot2docker vm being removed', function () { + + }); + + describe('and with a boot2docker vm initialized but not running', function () { + + }); + }); + +}); diff --git a/tests/SetupUtil-integration.js b/tests/SetupUtil-integration.js new file mode 100644 index 0000000000..b56ef2e2e0 --- /dev/null +++ b/tests/SetupUtil-integration.js @@ -0,0 +1,10 @@ +var setupUtil = require('../build/SetupUtil'); + +describe('SetupUtils', function() { + it('returns live sha256 checksum for a given virtualbox version & filename', function (done) { + setupUtil.virtualboxSHA256('4.3.20', 'VirtualBox-4.3.20-96996-OSX.dmg', function (err, checksum) { + expect(checksum).toBe('744e77119a640a5974160213c9912568a3d88dbd06a2fc6b6970070941732705'); + done(); + }); + }); +}); diff --git a/vendor/jasmine-2.1.3/boot.js b/tests/jasmine-2.1.3/boot.js similarity index 100% rename from vendor/jasmine-2.1.3/boot.js rename to tests/jasmine-2.1.3/boot.js diff --git a/vendor/jasmine-2.1.3/console.js b/tests/jasmine-2.1.3/console.js similarity index 98% rename from vendor/jasmine-2.1.3/console.js rename to tests/jasmine-2.1.3/console.js index a65876e911..96026ab63a 100755 --- a/vendor/jasmine-2.1.3/console.js +++ b/tests/jasmine-2.1.3/console.js @@ -57,8 +57,6 @@ getJasmineRequireObj().ConsoleReporter = function() { }, failedSuites = []; - print('ConsoleReporter is deprecated and will be removed in a future version.'); - this.jasmineStarted = function() { specCount = 0; failureCount = 0; diff --git a/vendor/jasmine-2.1.3/jasmine-html.js b/tests/jasmine-2.1.3/jasmine-html.js similarity index 100% rename from vendor/jasmine-2.1.3/jasmine-html.js rename to tests/jasmine-2.1.3/jasmine-html.js diff --git a/vendor/jasmine-2.1.3/jasmine.css b/tests/jasmine-2.1.3/jasmine.css similarity index 100% rename from vendor/jasmine-2.1.3/jasmine.css rename to tests/jasmine-2.1.3/jasmine.css diff --git a/vendor/jasmine-2.1.3/jasmine.js b/tests/jasmine-2.1.3/jasmine.js similarity index 100% rename from vendor/jasmine-2.1.3/jasmine.js rename to tests/jasmine-2.1.3/jasmine.js diff --git a/vendor/jasmine-2.1.3/jasmine_favicon.png b/tests/jasmine-2.1.3/jasmine_favicon.png similarity index 100% rename from vendor/jasmine-2.1.3/jasmine_favicon.png rename to tests/jasmine-2.1.3/jasmine_favicon.png diff --git a/tests/tests.html b/tests/tests.html new file mode 100755 index 0000000000..0d298c493c --- /dev/null +++ b/tests/tests.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/tests.js b/tests/tests.js new file mode 100644 index 0000000000..e21261d882 --- /dev/null +++ b/tests/tests.js @@ -0,0 +1,25 @@ +window.jasmineRequire = require('./jasmine-2.1.3/jasmine'); +require('./jasmine-2.1.3/jasmine-html'); +require('./jasmine-2.1.3/boot'); +var consoleReporter = require('./jasmine-2.1.3/console'); +var app = require('remote').require('app'); + +jasmine.getEnv().addReporter(new consoleReporter.ConsoleReporter()({ + showColors: true, + timer: new jasmine.Timer(), + print: function() { + process.stdout.write.apply(process.stdout, arguments); + }, + onComplete: function () { + app.quit(); + } + })); + +var fs = require('fs'); +var tests = fs.readdirSync('./tests').filter(function (f) { + return f.indexOf('-' + process.env.TEST_TYPE) !== -1; +}); + +tests.forEach(function (t) { + require('./' + t); +});