diff --git a/.gitignore b/.gitignore index e51ebc5468..8fe91862fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .swp build dist +installer node_modules coverage npm-debug.log diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000000..062ff65461 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,281 @@ +var path = require('path'); +var execFile = require('child_process').execFile; +var packagejson = require('./package.json'); +var electron = require('electron-prebuilt'); + +var WINDOWS_DOCKER_URL = 'https://get.docker.com/builds/Windows/x86_64/docker-1.6.2.exe'; +var DARWIN_DOCKER_URL = 'https://get.docker.com/builds/Darwin/x86_64/docker-' + packagejson['docker-version']; +var WINDOWS_DOCKER_MACHINE_URL = 'https://github.com/docker/machine/releases/download/v' + packagejson['docker-machine-version'] + '/docker-machine_windows-amd64.exe'; +var DARWIN_DOCKER_MACHINE_URL = 'https://github.com/docker/machine/releases/download/v' + packagejson['docker-machine-version'] + '/docker-machine_darwin-amd64'; +var DARWIN_COMPOSE_URL = 'https://github.com/docker/compose/releases/download/' + packagejson['docker-compose-version'] + '/docker-compose-Darwin-x86_64'; +var BOOT2DOCKER_ISO_URL = 'https://github.com/boot2docker/boot2docker/releases/download/v' + packagejson['docker-version'] + '/boot2docker.iso'; + +module.exports = function (grunt) { + require('load-grunt-tasks')(grunt); + var target = grunt.option('target') || 'development'; + var beta = grunt.option('beta') || false; + var env = process.env; + env.NODE_PATH = '..:' + env.NODE_PATH; + env.NODE_ENV = target; + + var version = function (str) { + var match = str.match(/(\d+\.\d+\.\d+)/); + return match ? match[1] : null; + }; + + grunt.registerMultiTask('download-binary', 'Downloads binary unless version up to date', function () { + var target = grunt.task.current.target; + var done = this.async(); + var config = grunt.config('download-binary')[target]; + execFile(config.binary, ['--version'], function (err, stdout) { + var currentVersion = version(stdout); + if (!currentVersion || currentVersion !== version(config.version)) { + grunt.task.run('curl:' + target); + grunt.task.run('chmod'); + } + done(); + }); + }); + + var APPNAME = beta ? 'Kitematic (Beta)' : 'Kitematic'; + var OSX_OUT = './dist/osx'; + var OSX_FILENAME = OSX_OUT + '/' + APPNAME + '.app'; + + grunt.initConfig({ + IDENTITY: 'Developer ID Application: Docker Inc', + APPNAME: APPNAME, + OSX_OUT: OSX_OUT, + OSX_FILENAME: OSX_FILENAME, + OSX_FILENAME_ESCAPED: OSX_FILENAME.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'), + + // electron + electron: { + windows: { + options: { + name: '<%= APPNAME %>', + dir: 'build/', + out: 'dist/', + version: packagejson['electron-version'], + platform: 'win32', + arch: 'x64', + asar: true, + icon: 'util/kitematic.ico' + } + }, + osx: { + options: { + name: '<%= APPNAME %>', + dir: 'build/', + out: '<%= OSX_OUT %>', + version: packagejson['electron-version'], + platform: 'darwin', + arch: 'x64', + asar: true, + 'app-bundle-id': 'com.kitematic.kitematic' + } + } + }, + + 'create-windows-installer': { + appDirectory: 'dist/Kitematic-win32/', + authors: 'Docker Inc.', + loadingGif: 'util/loading.gif', + setupIcon: 'util/kitematic.ico' + }, + + // docker binaries + 'download-binary': { + docker: { + version: packagejson['docker-version'], + binary: path.join('resources', 'docker'), + download: 'curl:docker' + }, + 'docker-machine': { + version: packagejson['docker-machine-version'], + binary: path.join('resources', 'docker-machine'), + download: 'curl:docker-machine' + } + }, + + // images + copy: { + dev: { + files: [{ + expand: true, + cwd: '.', + src: ['package.json', 'settings.json', 'index.html'], + dest: 'build/' + }, { + expand: true, + cwd: 'images/', + src: ['**/*'], + dest: 'build/' + }, { + expand: true, + cwd: 'fonts/', + src: ['**/*'], + dest: 'build/' + }, { + cwd: 'node_modules/', + src: Object.keys(packagejson.dependencies).map(function (dep) { return dep + '/**/*';}), + dest: 'build/node_modules/', + expand: true + }] + }, + windows: { + files: [{ + expand: true, + cwd: 'resources', + src: ['docker*'], + dest: 'dist/Kitematic-win32/resources/resources/' + }], + options: { + mode: true + } + }, + osx: { + files: [{ + expand: true, + cwd: 'resources', + src: ['**/*'], + dest: '<%= OSX_FILENAME %>/Contents/Resources/resources/' + }, { + src: 'util/kitematic.icns', + dest: '<%= OSX_FILENAME %>/Contents/Resources/atom.icns' + }], + options: { + mode: true + } + } + }, + + rename: { + installer: { + src: 'installer/Setup.exe', + dest: 'installer/KitematicSetup.exe' + } + }, + + // download binaries + curl: { + docker: { + src: process.platform === 'win32' ? WINDOWS_DOCKER_URL : DARWIN_DOCKER_URL, + dest: process.platform === 'win32' ? path.join('resources', 'docker.exe') : path.join('resources', 'docker') + }, + 'docker-machine': { + src: process.platform === 'win32' ? WINDOWS_DOCKER_MACHINE_URL : DARWIN_DOCKER_MACHINE_URL, + dest: process.platform === 'win32' ? path.join('resources', 'docker-machine.exe') : path.join('resources', 'docker-machine') + }, + 'docker-compose': { + src: DARWIN_COMPOSE_URL, + dest: 'resources/docker-compose' + }, + 'boot2docker-iso': { + src: BOOT2DOCKER_ISO_URL, + dest: path.join('resources', 'boot2docker-' + packagejson['docker-version']) + } + }, + + chmod: { + binaries: { + options: { + mode: '755' + }, + src: ['resources/docker*'] + } + }, + + // styles + less: { + options: { + sourceMapFileInline: true + }, + dist: { + files: { + 'build/main.css': 'styles/main.less' + } + } + }, + + // javascript + babel: { + options: { + sourceMap: 'inline', + blacklist: 'regenerator' + }, + dist: { + files: [{ + expand: true, + cwd: 'src/', + src: ['**/*.js'], + dest: 'build/', + }] + } + }, + + shell: { + electron: { + command: electron + ' ' + 'build', + options: { + async: true, + execOptions: { + env: env + } + } + }, + sign: { + options: { + failOnError: false, + }, + command: [ + 'codesign --deep -v -f -s "<%= IDENTITY %>" <%= OSX_FILENAME_ESCAPED %>/Contents/Frameworks/*', + 'codesign -v -f -s "<%= IDENTITY %>" <%= OSX_FILENAME_ESCAPED %>', + 'codesign -vvv --display <%= OSX_FILENAME_ESCAPED %>', + 'codesign -v --verify <%= OSX_FILENAME_ESCAPED %>', + ].join(' && '), + }, + zip: { + command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> <%= OSX_OUT %>/Kitematic-' + packagejson.version + '.zip', + } + }, + + clean: { + release: ['build/', 'dist/', 'installer/'], + }, + + // livereload + watchChokidar: { + options: { + spawn: true + }, + livereload: { + options: {livereload: true}, + files: ['build/**/*'] + }, + js: { + files: ['src/**/*.js'], + tasks: ['newer:babel'] + }, + less: { + files: ['styles/**/*.less'], + tasks: ['newer:less'] + }, + copy: { + files: ['images/*', 'index.html', 'fonts/*'], + tasks: ['newer:copy:dev'] + } + } + }); + grunt.registerTask('default', ['download-binary', 'newer:babel', 'newer:less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']); + + if (process.platform === 'win32') { + grunt.registerTask('release', ['clean', 'download-binary', 'babel', 'less', 'copy:dev', 'electron:windows', 'copy:windows', 'create-windows-installer', 'rename:installer']); + } else { + grunt.registerTask('release', ['clean:dist', 'clean:build', 'download-binary', 'babel', 'less', 'copy:dev', 'electron:osx', 'copy:osx', 'shell:sign', 'shell:zip']); + } + + process.on('SIGINT', function () { + grunt.task.run(['shell:electron:kill']); + process.exit(1); + }); +}; diff --git a/package.json b/package.json index a2ee190495..f77fa685af 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "start": "gulp", "test": "jest -c jest-unit.json", "integration": "jest -c jest-integration.json", - "release": "gulp release", - "release:beta": "gulp release --beta", + "release": "grunt release", + "release:beta": "grunt release --beta=true", "lint": "jsxhint src", "reset": "gulp reset" }, @@ -67,25 +67,30 @@ "devDependencies": { "babel": "^5.1.10", "babel-jest": "^5.2.0", - "gulp": "^3.8.11", - "gulp-babel": "^5.1.0", - "gulp-changed": "^1.2.1", - "gulp-concat": "^2.5.2", - "gulp-cssmin": "^0.1.6", - "gulp-download-electron": "^0.0.5", - "gulp-if": "^1.2.5", - "gulp-insert": "^0.4.0", - "gulp-less": "^3.0.2", - "gulp-livereload": "^3.8.0", - "gulp-plumber": "^1.0.0", - "gulp-shell": "^0.4.1", - "gulp-sourcemaps": "^1.5.2", - "gulp-util": "^3.0.4", + "electron-prebuilt": "^0.27.3", + "grunt": "^0.4.5", + "grunt-babel": "^5.0.1", + "grunt-chmod": "^1.0.3", + "grunt-cli": "^0.1.13", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-copy": "^0.8.0", + "grunt-contrib-less": "^1.0.1", + "grunt-contrib-watch-chokidar": "^1.0.0", + "grunt-curl": "^2.2.0", + "grunt-download-electron": "^2.1.1", + "grunt-electron": "^1.0.0", + "grunt-electron-installer": "^0.33.0", + "grunt-newer": "^1.1.1", + "grunt-rename": "^0.1.4", + "grunt-shell": "^1.1.2", + "grunt-shell-spawn": "^0.3.8", "jest-cli": "^0.4.5", "jsxhint": "^0.14.0", + "load-grunt-tasks": "^3.2.0", "minimist": "^1.1.1", "react-tools": "^0.13.1", "run-sequence": "^1.0.2", + "shell-escape": "^0.2.0", "source-map-support": "^0.2.10" } } diff --git a/src/browser.js b/src/browser.js index a9f087e7c8..bc0979d16b 100644 --- a/src/browser.js +++ b/src/browser.js @@ -5,9 +5,8 @@ var fs = require('fs'); var ipc = require('ipc'); var path = require('path'); -process.env.NODE_PATH = path.join(__dirname, '/../node_modules'); +process.env.NODE_PATH = path.join(__dirname, 'node_modules'); process.env.RESOURCES_PATH = path.join(__dirname, '/../resources'); -process.chdir(path.join(__dirname, '..')); process.env.PATH = '/usr/local/bin:' + process.env.PATH; var size = {}, settingsjson = {}; @@ -15,9 +14,49 @@ try { size = JSON.parse(fs.readFileSync(path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], 'Library', 'Application\ Support', 'Kitematic', 'size'))); } catch (err) {} try { - settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); + settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, 'settings.json'), 'utf8')); } catch (err) {} +var handleStartupEvent = function() { + if (process.platform !== 'win32') { + return false; + } + + var squirrelCommand = process.argv[1]; + switch (squirrelCommand) { + case '--squirrel-install': + case '--squirrel-updated': + + // Optionally do things such as: + // + // - Install desktop and start menu shortcuts + // - Add your .exe to the PATH + // - Write to the registry for things like file associations and + // explorer context menus + + // Always quit when done + app.quit(); + + return true; + case '--squirrel-uninstall': + // Undo anything you did in the --squirrel-install and + // --squirrel-updated handlers + + // Always quit when done + app.quit(); + + return true; + case '--squirrel-obsolete': + // This is called on the outgoing version of your app before + // we update to the new version - it's the opposite of + // --squirrel-updated + app.quit(); + return true; + } +}; + +handleStartupEvent(); + var openURL = null; app.on('open-url', function (event, url) { event.preventDefault(); @@ -36,7 +75,7 @@ app.on('ready', function () { show: false, }); - mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'build/index.html'))); + mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, 'index.html'))); app.on('activate-with-no-open-windows', function () { if (mainWindow) { @@ -52,7 +91,8 @@ app.on('ready', function () { }); app.on('before-quit', function () { - if (!updating) { + // TODO: make this work for right click + close + if (!updating && mainWindow.webContents) { mainWindow.webContents.send('application:quitting'); } }); diff --git a/src/components/ContainerDetailsSubheader.react.js b/src/components/ContainerDetailsSubheader.react.js index f0cedecc95..e4793d6d5b 100644 --- a/src/components/ContainerDetailsSubheader.react.js +++ b/src/components/ContainerDetailsSubheader.react.js @@ -1,13 +1,18 @@ +var $ = require('jquery'); var _ = require('underscore'); var React = require('react'); var exec = require('exec'); var shell = require('shell'); var metrics = require('../utils/MetricsUtil'); var ContainerUtil = require('../utils/ContainerUtil'); +var util = require('../utils/Util'); var machine = require('../utils/DockerMachineUtil'); +var RetinaImage = require('react-retina-image'); var classNames = require('classnames'); var resources = require('../utils/ResourcesUtil'); +var dockerUtil = require('../utils/DockerUtil'); var containerActions = require('../actions/ContainerActions'); +var dockerMachineUtil = require('../utils/DockerMachineUtil'); var ContainerDetailsSubheader = React.createClass({ contextTypes: { @@ -101,15 +106,7 @@ var ContainerDetailsSubheader = React.createClass({ if(!shell) { shell = 'sh'; } - machine.ip().then(ip => { - var cmd = [resources.terminal(), 'ssh', '-p', '22', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'LogLevel=quiet', '-o', 'StrictHostKeyChecking=no', '-i', '~/.docker/machine/machines/' + machine.name() + '/id_rsa', 'docker@' + ip, '-t', 'docker', - 'exec', '-i', '-t', container.Name, shell]; - exec(cmd, function (stderr, stdout, code) { - if (code) { - console.log(stderr); - } - }); - }); + dockerMachineUtil.dockerTerminal(`docker exec -it ${this.props.container.Name} ${shell}`); } }, render: function () { diff --git a/src/components/ContainerHomeFolders.react.js b/src/components/ContainerHomeFolders.react.js index e2fbf89e78..f2b9912684 100644 --- a/src/components/ContainerHomeFolders.react.js +++ b/src/components/ContainerHomeFolders.react.js @@ -29,12 +29,7 @@ var ContainerHomeFolder = React.createClass({ volumes[containerVolume] = newHostVolume; var binds = _.pairs(volumes).map(function (pair) { if(util.isWindows()) { - var home = util.home(); - home = home.charAt(0).toLowerCase() + home.slice(1); - home = '/' + home.replace(':', '').replace(/\\/g, '/'); - var fullPath = path.join(home, 'Kitematic', pair[1], pair[0]); - fullPath = fullPath.replace(/\\/g, '/'); - return fullPath + ':' + pair[0]; + return util.windowsToLinuxPath(pair[1]) + ':' + pair[0]; } return pair[1] + ':' + pair[0]; }); diff --git a/src/components/ContainerSettingsVolumes.react.js b/src/components/ContainerSettingsVolumes.react.js index b1b828b686..fe2572eae7 100644 --- a/src/components/ContainerSettingsVolumes.react.js +++ b/src/components/ContainerSettingsVolumes.react.js @@ -3,6 +3,7 @@ var React = require('react/addons'); var remote = require('remote'); var dialog = remote.require('dialog'); var shell = require('shell'); +var util = require('../utils/Util'); var metrics = require('../utils/MetricsUtil'); var containerActions = require('../actions/ContainerActions'); @@ -15,7 +16,10 @@ var ContainerSettingsVolumes = React.createClass({ } var directory = filenames[0]; if (directory) { - metrics.track('Chose Directory for Volume'); + metrics.track('Choose Directory for Volume'); + if(util.isWindows()) { + directory = util.windowsToLinuxPath(directory); + } var volumes = _.clone(self.props.container.Volumes); volumes[dockerVol] = directory; var binds = _.pairs(volumes).map(function (pair) { @@ -47,6 +51,7 @@ var ContainerSettingsVolumes = React.createClass({ if (!this.props.container) { return false; } + var volumes = _.map(this.props.container.Volumes, (val, key) => { if (!val || val.indexOf(process.env.HOME) === -1) { val = ( diff --git a/src/components/Containers.react.js b/src/components/Containers.react.js index 449afb4835..521001629a 100644 --- a/src/components/Containers.react.js +++ b/src/components/Containers.react.js @@ -6,6 +6,7 @@ var containerStore = require('../stores/ContainerStore'); var ContainerList = require('./ContainerList.react'); var Header = require('./Header.react'); var metrics = require('../utils/MetricsUtil'); +var RetinaImage = require('react-retina-image'); var shell = require('shell'); var machine = require('../utils/DockerMachineUtil'); diff --git a/src/stores/SetupStore.js b/src/stores/SetupStore.js index 588d2c07e8..7c99a0ff64 100644 --- a/src/stores/SetupStore.js +++ b/src/stores/SetupStore.js @@ -42,14 +42,14 @@ var _steps = [{ progressCallback(50); // TODO: detect when the installation has started so we can simulate progress try { if (util.isWindows()) { - yield util.exec([path.join(util.supportDir(), virtualBox.filename())]); + yield util.exec([path.join(util.supportDir(), virtualBox.filename()), '-msiparams', 'REBOOT=ReallySuppress', 'LIMITUI=INSTALLUILEVEL_PROGRESSONLY']); } else { yield util.exec(setupUtil.macSudoCmd(setupUtil.installVirtualBoxCmd())); } } catch (err) { throw null; } - } else if (!virtualBox.active()) { + } else if (util.isWindows() && !virtualBox.active()) { yield util.exec(setupUtil.macSudoCmd(util.escapePath('/Library/Application Support/VirtualBox/LaunchDaemons/VirtualBoxStartup.sh') + ' restart')); } }) @@ -59,7 +59,7 @@ var _steps = [{ message: 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...', totalPercent: 60, percent: 0, - seconds: 72, + seconds: 80, run: Promise.coroutine(function* (progressCallback) { setupUtil.simulateProgress(this.seconds, progressCallback); var exists = yield machine.exists(); @@ -68,16 +68,6 @@ var _steps = [{ yield machine.rm(); } yield machine.create(); - if(util.isWindows()) { - let home = util.home(); - let driveLetter = home.charAt(0); - let parts = home.split('\\').slice(0, -1); - let usersDirName = parts[parts.length-1]; - let usersDirPath = parts.join('\\'); - let shareName = driveLetter + '/' + usersDirName; - yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath); - yield machine.start(); - } return; } @@ -159,7 +149,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), { var vboxNeedsInstall = !virtualBox.installed(); required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== virtualBox.checksum()); - required.install = vboxNeedsInstall || !virtualBox.active(); + required.install = vboxNeedsInstall || (util.isWindows() && !virtualBox.active()); required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || util.compareVersions(isoversion, packagejson['docker-version']) < 0; var exists = yield machine.exists(); diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index 0875882d5c..37532dc690 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -153,14 +153,23 @@ var DockerMachine = { }); }); }, - dockerTerminal: function () { + dockerTerminal: function (cmd) { if(util.isWindows()) { + cmd = cmd || ''; this.info().then(machine => { - util.execProper(`start cmd.exe /k "SET DOCKER_HOST=${machine.url}&& SET DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)}&& SET DOCKER_TLS_VERIFY=1`); + util.exec('start powershell.exe ' + cmd, + {env: { + 'DOCKER_HOST' : machine.url, + 'DOCKER_CERT_PATH' : path.join(util.home(), '.docker/machine/machines/' + machine.name), + 'DOCKER_TLS_VERIFY': 1, + 'PATH': resources.resourceDir() + } + }); }); } else { + cmd = cmd || '$SHELL'; this.info().then(machine => { - var cmd = [resources.terminal(), `DOCKER_HOST=${machine.url} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)} DOCKER_TLS_VERIFY=1 $SHELL`]; + var cmd = [resources.terminal(), `DOCKER_HOST=${machine.url} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)} DOCKER_TLS_VERIFY=1 ${cmd}`]; util.exec(cmd).then(() => {}); }); } diff --git a/src/utils/MetricsUtil.js b/src/utils/MetricsUtil.js index e7083989fd..8b19edfbd0 100644 --- a/src/utils/MetricsUtil.js +++ b/src/utils/MetricsUtil.js @@ -7,7 +7,7 @@ var util = require('./Util'); var settings; try { - settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'settings.json'), 'utf8')); + settings = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); } catch (err) { settings = {}; } diff --git a/src/utils/RegHubUtil.js b/src/utils/RegHubUtil.js index bb7b1f5734..cf00640249 100644 --- a/src/utils/RegHubUtil.js +++ b/src/utils/RegHubUtil.js @@ -5,6 +5,7 @@ var util = require('../utils/Util'); var hubUtil = require('../utils/HubUtil'); var repositoryServerActions = require('../actions/RepositoryServerActions'); var tagServerActions = require('../actions/TagServerActions'); +var Promise = require('bluebird'); let REGHUB2_ENDPOINT = process.env.REGHUB2_ENDPOINT || 'https://registry.hub.docker.com/v2'; let searchReq = null; @@ -61,7 +62,7 @@ module.exports = { let data = JSON.parse(body); let repos = data.repos; async.map(repos, (repo, cb) => { - var name = repo.repo; + let name = repo.repo; if (util.isOfficialRepo(name)) { name = 'library/' + name; } diff --git a/src/utils/Util.js b/src/utils/Util.js index 47d54b16f4..4566025bf5 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -1,4 +1,5 @@ var exec = require('exec'); +var child_process = require('child_process'); var Promise = require('bluebird'); var fs = require('fs'); var path = require('path'); @@ -9,11 +10,12 @@ var app = remote.require('app'); module.exports = { exec: function (args, options) { options = options || {}; + let fn = Array.isArray(args) ? exec : child_process.exec; return new Promise((resolve, reject) => { - exec(args, options, (stderr, stdout, code) => { + fn(args, options, (stderr, stdout, code) => { if (code) { var cmd = Array.isArray(args) ? args.join(' ') : args; - reject(new Error(cmd + ' returned non zero exit code.\n===== Stderr =====\n ' + stderr + '\n===== Stdout =====\n' + stdout)); + reject(new Error(cmd + ' returned non zero exit code. Stderr: ' + stderr)); } else { resolve(stdout); } @@ -59,12 +61,12 @@ module.exports = { .replace(/\/Users\/[^\/]*\//mg, '/Users//'); }, packagejson: function () { - return JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'package.json'), 'utf8')); + return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); }, settingsjson: function () { var settingsjson = {}; try { - settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'settings.json'), 'utf8')); + settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); } catch (err) {} return settingsjson; }, @@ -132,5 +134,12 @@ module.exports = { randomId: function () { return crypto.randomBytes(32).toString('hex'); }, + windowsToLinuxPath: function(windowsAbsPath) { + var fullPath = windowsAbsPath.replace(':', '').split(path.sep).join('/'); + if(fullPath.charAt(0) !== '/'){ + fullPath = '/' + fullPath.charAt(0).toLowerCase() + fullPath.substring(1); + } + return fullPath; + }, webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983'] }; diff --git a/util/kitematic.ico b/util/kitematic.ico new file mode 100644 index 0000000000..ec7dab48ab Binary files /dev/null and b/util/kitematic.ico differ diff --git a/util/loading.gif b/util/loading.gif new file mode 100644 index 0000000000..3f6d3b671a Binary files /dev/null and b/util/loading.gif differ