diff --git a/browser/main.js b/browser/main.js
index 8c31209cdd..ee51d8d578 100644
--- a/browser/main.js
+++ b/browser/main.js
@@ -11,10 +11,18 @@ var BrowserWindow = require('browser-window');
var ipc = require('ipc');
var argv = require('minimist')(process.argv);
+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();
@@ -23,38 +31,35 @@ 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
- };
+ frame: false,
+ show: false
+ });
- mainWindow = new BrowserWindow(windowOptions);
- mainWindow.hide();
if (argv.test) {
- mainWindow.loadUrl('file://' + __dirname + '/../specs/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('');
@@ -92,12 +97,12 @@ app.on('ready', function() {
autoUpdater.quitAndInstall();
}
});
+
+ autoUpdater.checkForUpdates();
}
ipc.on('vm', function (event, arg) {
saveVMOnQuit = arg;
});
-
- autoUpdater.checkForUpdates();
});
});
diff --git a/deps b/deps
index e01dfa4f77..7e1cbc3cc2 100755
--- a/deps
+++ b/deps
@@ -1,36 +1,15 @@
#!/bin/bash
-
BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-PLATFORM='unknown'
-unamestr=`uname`
-if [[ "$unamestr" == 'Linux' ]]; then
- PLATFORM='linux'
-elif [[ "$unamestr" == 'Darwin' ]]; then
- PLATFORM='darwin'
-fi
-
-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
-export ATOM_SHELL_VERSION=$(node -pe "JSON.parse(process.argv[1])['atom-shell-version']" "$(cat $BASE/package.json)")
-export ATOM_SHELL_VERSION_FILE=atom-shell-v$ATOM_SHELL_VERSION-$PLATFORM-x64.zip
-
-mkdir -p $BASE/cache/$PLATFORM
-pushd $BASE/cache/$PLATFORM > /dev/null
-if [ ! -f $ATOM_SHELL_VERSION_FILE ]; then
- echo "-----> Downloading atom-shell..."
- rm -rf atom-shell-*
- curl -L -o $ATOM_SHELL_VERSION_FILE https://github.com/atom/atom-shell/releases/download/v$ATOM_SHELL_VERSION/atom-shell-v$ATOM_SHELL_VERSION-$PLATFORM-x64.zip
- unzip $ATOM_SHELL_VERSION_FILE
-fi
-popd > /dev/null
+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/resources/virtualbox-4.3.18.pkg b/gulp
similarity index 100%
rename from resources/virtualbox-4.3.18.pkg
rename to gulp
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000000..546108ce42
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,170 @@
+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 plumber = require('gulp-plumber');
+var react = require('gulp-react');
+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('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 || options.test) ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
+ .pipe(gulpif(options.dev, livereload()));
+});
+
+gulp.task('images', function() {
+ 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('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
+ this.emit('end');
+ }))
+ .pipe(gulpif(options.dev, sourcemaps.init()))
+ .pipe(less())
+ .pipe(gulpif(options.dev, sourcemaps.write()))
+ .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, livereload()));
+});
+
+gulp.task('download', function (cb) {
+ downloadatomshell({
+ version: packagejson['atom-shell-version'],
+ outputDir: 'cache'
+ }, cb);
+});
+
+gulp.task('copy', function () {
+ 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('fonts/**')
+ .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
+ .pipe(gulpif(options.dev, livereload()));
+});
+
+gulp.task('dist', function (cb) {
+ var stream = gulp.src('').pipe(shell([
+ 'rm -Rf ./dist',
+ 'mkdir -p ./dist/osx',
+ 'cp -R ./cache/Atom.app ./dist/osx/<%= filename %>',
+ 'mv ./dist/osx/<%= filename %>/Contents/MacOS/Atom ./dist/osx/<%= filename %>/Contents/MacOS/<%= name %>',
+ 'mkdir -p ./dist/osx/<%= filename %>/Contents/Resources/app',
+ 'cp -R browser dist/osx/<%= filename %>/Contents/Resources/app',
+ 'cp package.json dist/osx/<%= filename %>/Contents/Resources/app/',
+ 'mkdir -p dist/osx/<%= filename %>/Contents/Resources/app/resources',
+ 'cp -v resources/* dist/osx/<%= filename %>/Contents/Resources/app/resources/ || :',
+ 'cp kitematic.icns dist/osx/<%= filename %>/Contents/Resources/atom.icns',
+ '/usr/libexec/PlistBuddy -c "Set :CFBundleVersion <%= version %>" dist/osx/<%= filename %>/Contents/Info.plist',
+ '/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist',
+ '/usr/libexec/PlistBuddy -c "Set :CFBundleName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist',
+ '/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier <%= bundle %>" dist/osx/<%= filename %>/Contents/Info.plist',
+ '/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist'
+ ], {
+ templateData: {
+ filename: options.filename,
+ name: options.name,
+ version: packagejson.version,
+ bundle: 'com.kitematic.app'
+ }
+ }));
+
+ dependencies.forEach(function (d) {
+ stream = stream.pipe(shell([
+ 'cp -R node_modules/' + d + ' dist/osx/<%= filename %>/Contents/Resources/app/node_modules/'
+ ], {
+ templateData: {
+ filename: options.filename
+ }
+ }));
+ });
+
+ return stream;
+});
+
+gulp.task('sign', function () {
+ try {
+ var signing_identity = fs.readFileSync('./identity', 'utf8').trim();
+ return gulp.src('').pipe(shell([
+ 'codesign --deep --force --verbose --sign "' + signing_identity + '" ' + options.filename
+ ], {
+ cwd: './dist/osx/'
+ }));
+ } catch (error) {
+ gutil.log(gutil.colors.red('Error: ' + error.message));
+ }
+});
+
+gulp.task('zip', function () {
+ return gulp.src('').pipe(shell([
+ 'ditto -c -k --sequesterRsrc --keepParent ' + options.filename + ' ' + options.name + '-' + packagejson.version + '.zip'
+ ], {
+ cwd: './dist/osx/'
+ }));
+});
+
+gulp.task('release', function () {
+ runSequence('download', 'dist', ['copy', 'images', 'js', 'styles'], 'sign', 'zip');
+});
+
+gulp.task('test', ['download', 'copy', 'js'], function () {
+ var env = process.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('src/**/*.js', ['js']);
+ gulp.watch('index.html', ['copy']);
+ gulp.watch('styles/**/*.less', ['styles']);
+ gulp.watch('images/**', ['images']);
+
+ livereload.listen();
+
+ var env = process.env;
+ env.NODE_ENV = 'development';
+ gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom .'], {
+ env: env
+ }));
+});
diff --git a/package.json b/package.json
index de2dd005a1..a80e514676 100644
--- a/package.json
+++ b/package.json
@@ -11,13 +11,12 @@
},
"bugs": "https://github.com/kitematic/kitematic/issues",
"scripts": {
- "start": "rsync ./index.html ./build/ && rsync ./fonts/* ./build/ && rsync ./images/* ./build && jsx --watch src/ build/ & wess -w -m -i ./styles/main.less -o ./build/main.css & wiper -w ./build/**/*.* & npm run start:$(uname) && kill $(pgrep node)",
- "start:Darwin": "NODE_ENV=development ./cache/darwin/Atom.app/Contents/MacOS/Atom .",
- "start:Linux": "NODE_ENV=development ./cache/linux/atom .",
- "test": "jsx --watch src build/ & rsync ./images/* ./build && wess -m -i ./styles/main.less -o ./build/main.css && npm run test:$(uname) && kill $(pgrep node)",
- "test:Darwin": "NODE_ENV=test ./cache/darwin/Atom.app/Contents/MacOS/Atom . --test",
- "test:Linux": "NODE_ENV=test ./cache/darwin/atom .",
- "release": "./release"
+ "start": "gulp",
+ "test": "gulp test --silent",
+ "integration-tests": "gulp test --silent --integration",
+ "all-tests": "npm test && npm run integration-tests",
+ "release": "gulp run release",
+ "preinstall": "./deps"
},
"licenses": [
{
@@ -32,15 +31,17 @@
"debug"
]
},
- "boot2docker-version": "1.3.2",
+ "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": "^2.4.6",
+ "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",
@@ -57,15 +58,33 @@
"underscore": "^1.7.0"
},
"devDependencies": {
+ "browserify": "^6.2.0",
"ecstatic": "^0.5.8",
"glob": "^4.0.6",
- "jasmine-node": "^1.14.5",
- "jasmine-reporters": "^1.0.1",
- "jasmine-tagged": "^1.1.2",
- "jasmine-terminal-reporter": "^0.9.1",
- "react-tools": "^0.12.2",
- "wess": "^0.2.2",
- "wiper": "^1.0.2",
+ "gulp": "^3.8.10",
+ "gulp-atom": "0.0.5",
+ "gulp-concat": "^2.3.4",
+ "gulp-cssmin": "^0.1.6",
+ "gulp-download-atom-shell": "0.0.4",
+ "gulp-if": "^1.2.4",
+ "gulp-imagemin": "^2.0.0",
+ "gulp-less": "^2.0.1",
+ "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",
+ "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",
"zombie": "^2.5.1"
}
}
diff --git a/release b/release
deleted file mode 100755
index 7aa58dac37..0000000000
--- a/release
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-export NAME=$(node -pe "JSON.parse(process.argv[1])['name']" "$(cat $BASE/package.json)")
-export VERSION=$(node -pe "JSON.parse(process.argv[1])['version']" "$(cat $BASE/package.json)")
-export FILENAME=$NAME.app
-export BUNDLE=com.kitematic.app
-
-rm -Rf ./dist
-mkdir -p ./dist/osx
-cp -R ./cache/Atom.app ./dist/osx/$FILENAME
-mv ./dist/osx/$FILENAME/Contents/MacOS/Atom ./dist/osx/$FILENAME/Contents/MacOS/$NAME
-mkdir -p ./dist/osx/$FILENAME/Contents/Resources/app
-cp -R browser dist/osx/$FILENAME/Contents/Resources/app
-cp package.json dist/osx/$FILENAME/Contents/Resources/app/
-mkdir -p dist/osx/$FILENAME/Contents/Resources/app/resources
-mkdir -p dist/osx/$FILENAME/Contents/Resources/app/node_modules
-cp -v resources/* dist/osx/$FILENAME/Contents/Resources/app/resources/ || :
-cp kitematic.icns dist/osx/$FILENAME/Contents/Resources/atom.icns
-/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" dist/osx/$FILENAME/Contents/Info.plist
-/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $NAME" dist/osx/$FILENAME/Contents/Info.plist
-/usr/libexec/PlistBuddy -c "Set :CFBundleName $NAME" dist/osx/$FILENAME/Contents/Info.plist
-/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE" dist/osx/$FILENAME/Contents/Info.plist
-/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable $NAME" dist/osx/$FILENAME/Contents/Info.plist
-
-rsync ./index.html ./dist/osx/$FILENAME/Contents/Resources/app/build/
-rsync ./fonts/* ./dist/osx/$FILENAME/Contents/Resources/app/build/
-rsync ./images/* ./dist/osx/$FILENAME/Contents/Resources/app/build/
-jsx src/ ./dist/osx/$FILENAME/Contents/Resources/app/build/
-wess -m -i ./styles/main.less -o ./dist/osx/$FILENAME/Contents/Resources/app/build/main.css
-
-if [ -f $BASE/identity ]; then
- codesign --deep --force --verbose --sign "$(cat $BASE/identity)" ./dist/osx/$FILENAME
-fi
-
-ditto -c -k --sequesterRsrc --keepParent ./dist/osx/$FILENAME ./dist/osx/$NAME-$VERSION.zip
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/specs/ContainerStore-spec.js b/specs/ContainerStore-spec.js
deleted file mode 100755
index 5c8edb3a4c..0000000000
--- a/specs/ContainerStore-spec.js
+++ /dev/null
@@ -1,8 +0,0 @@
-var ContainerStore = require('../build/ContainerStore');
-var TestUtils = require('react/addons').TestUtils;
-
-describe('ContainerStore', function() {
- it('returns an empty array initially', function() {
- expect(ContainerStore.containers()).toEqual({});
- });
-});
diff --git a/specs/tests.js b/specs/tests.js
new file mode 100644
index 0000000000..394df65268
--- /dev/null
+++ b/specs/tests.js
@@ -0,0 +1,26 @@
+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();
+ }
+}));
+
+console.log('hi');
+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);
+});
diff --git a/src/Main.js b/src/Main.js
index ae2d5bc526..d908e18656 100644
--- a/src/Main.js
+++ b/src/Main.js
@@ -6,19 +6,18 @@ 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;
-
if (process.env.NODE_ENV === 'development') {
var script = document.createElement('script');
script.type = 'text/javascript';
@@ -28,8 +27,16 @@ 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) {
+ docker.setHost(ip);
+ router.transitionTo('containers');
+ ContainerStore.init(function () {
+ router.run(function (Handler) {
+ React.render(, document.body);
+ });
+ });
+ });
});
} else {
boot2docker.ip(function (err, ip) {
diff --git a/src/Setup.react.js b/src/Setup.react.js
index 5a77893b5a..c86f389ed4 100644
--- a/src/Setup.react.js
+++ b/src/Setup.react.js
@@ -5,133 +5,9 @@ var async = require('async');
var assign = require('object-assign');
var fs = require('fs');
var path = require('path');
-var boot2docker = require('./Boot2Docker');
var virtualbox = require('./Virtualbox');
var util = require('./Util');
-var docker = require('./Docker');
-var ContainerStore = require('./ContainerStore');
-
-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 SetupStore = require('./SetupStore');
var Setup = React.createClass({
mixins: [ Router.Navigation ],
@@ -140,6 +16,14 @@ var Setup = React.createClass({
message: '',
progress: 0
};
+ },
+ componentWillMount: function () {
+ SetupStore.on(SetupStore.PROGRESS_EVENT, this.update);
+ },
+ componentDidMount: function () {
+ },
+ update: function () {
+
},
render: function () {
var radial;
@@ -165,53 +49,6 @@ var Setup = React.createClass({
);
}
- },
- 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();
- }
- });
}
});
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/src/Util.js b/src/SetupUtil.js
similarity index 66%
rename from src/Util.js
rename to src/SetupUtil.js
index 78a4c61035..d3b8d696e9 100644
--- a/src/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/src/Virtualbox.js b/src/Virtualbox.js
index 503d4efad2..446537c461 100644
--- a/src/Virtualbox.js
+++ b/src/Virtualbox.js
@@ -5,32 +5,18 @@ var async = require('async');
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('/usr/bin/VBoxManage list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} /usr/bin/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,8 +66,9 @@ 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; }
exec('/usr/bin/VBoxManage controlvm ' + name + ' acpipowerbutton', function (stderr, stdout, code) {
@@ -91,7 +78,7 @@ var VirtualBox = {
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);
diff --git a/tests/SetupStore-integration.js b/tests/SetupStore-integration.js
new file mode 100644
index 0000000000..4060a12812
--- /dev/null
+++ b/tests/SetupStore-integration.js
@@ -0,0 +1,100 @@
+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').andCallFake(function (callback) {
+ callback(false);
+ });
+ });
+
+ it('downloads virtualbox', 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();
+ 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/specs/jasmine-2.1.3/boot.js b/tests/jasmine-2.1.3/boot.js
similarity index 100%
rename from specs/jasmine-2.1.3/boot.js
rename to tests/jasmine-2.1.3/boot.js
diff --git a/specs/jasmine-2.1.3/console.js b/tests/jasmine-2.1.3/console.js
similarity index 98%
rename from specs/jasmine-2.1.3/console.js
rename to tests/jasmine-2.1.3/console.js
index a65876e911..96026ab63a 100755
--- a/specs/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/specs/jasmine-2.1.3/jasmine-html.js b/tests/jasmine-2.1.3/jasmine-html.js
similarity index 100%
rename from specs/jasmine-2.1.3/jasmine-html.js
rename to tests/jasmine-2.1.3/jasmine-html.js
diff --git a/specs/jasmine-2.1.3/jasmine.css b/tests/jasmine-2.1.3/jasmine.css
similarity index 100%
rename from specs/jasmine-2.1.3/jasmine.css
rename to tests/jasmine-2.1.3/jasmine.css
diff --git a/specs/jasmine-2.1.3/jasmine.js b/tests/jasmine-2.1.3/jasmine.js
similarity index 100%
rename from specs/jasmine-2.1.3/jasmine.js
rename to tests/jasmine-2.1.3/jasmine.js
diff --git a/specs/jasmine-2.1.3/jasmine_favicon.png b/tests/jasmine-2.1.3/jasmine_favicon.png
similarity index 100%
rename from specs/jasmine-2.1.3/jasmine_favicon.png
rename to tests/jasmine-2.1.3/jasmine_favicon.png
diff --git a/specs/specs.html b/tests/tests.html
similarity index 59%
rename from specs/specs.html
rename to tests/tests.html
index 6cef94b504..0d298c493c 100755
--- a/specs/specs.html
+++ b/tests/tests.html
@@ -4,7 +4,6 @@
-
-
+