diff --git a/install.js b/install.js index a3a8dd5816..c3f54cc1de 100644 --- a/install.js +++ b/install.js @@ -3,7 +3,6 @@ var prompt = require('prompt'), async = require('async'), fs = require('fs'), path = require('path'), - colors = require('colors'), rimraf = require('rimraf'), exec = require('child_process').exec, builder = require('./lib/application'), @@ -124,7 +123,8 @@ var configItems = [ { name: 'smtpUsername', type: 'string', - description: "SMTP username" + description: "SMTP username", + default: '' }, { name: 'smtpPassword', @@ -135,7 +135,8 @@ var configItems = [ { name: 'fromAddress', type: 'string', - description: "Sender email address" + description: "Sender email address", + default: '' }, { name: 'outputPlugin', @@ -149,7 +150,7 @@ tenantConfig = [ { name: 'name', type: 'string', - description: "Set a unique name for your master tenant", + description: "Set a unique name for your tenant", pattern: /^[A-Za-z0-9_-]+\W*$/, default: 'master' }, @@ -224,7 +225,7 @@ var steps = [ // run the app app.run(); app.on('serverStarted', function () { - console.log("You will now be prompted to enter details for the master tenant."); + console.log("You will now be prompted to enter details for your tenant."); prompt.get(tenantConfig, function (err, result) { if (err) { console.log('ERROR: ', err); @@ -242,7 +243,7 @@ var steps = [ // create the tenant according to the user provided details var _createTenant = function (cb) { - console.log("Creating master tenant file system for " + (tenantName).blue + ", please wait ..."); + console.log("Creating file system for " + tenantName + ", please wait ..."); app.tenantmanager.createTenant({ name: tenantName, displayName: tenantDisplayName, @@ -262,7 +263,7 @@ var steps = [ } masterTenant = tenant; - console.log("Master tenant " + (tenant.name).blue + " was created."); + console.log("Tenant " + tenant.name + " was created."); // save master tenant name to config configuration.setConfig('masterTenantName', tenant.name); configuration.setConfig('masterTenantID', tenant._id); @@ -316,7 +317,7 @@ var steps = [ return exitInstall(1, 'Plugin install was unsuccessful. Please check the console output.'); } - console.log((' installing ' + plugin.getPluginType() + ' plugins').grey); + console.log(' installing ' + plugin.getPluginType() + ' plugins'); plugin.updatePackages(plugin.bowerConfig, { tenantId: masterTenant._id.toString(), skipTenantCopy: true }, cb); }); }, @@ -372,8 +373,8 @@ var steps = [ var proc = exec('grunt build:prod', { stdio: [0, 'pipe', 'pipe'] }, function (err) { if (err) { console.log('ERROR: ', err); - console.log('grunt build:prod command failed. Is the grunt-cli module installed? You can install using ' + 'npm install -g grunt grunt-cli'.grey); - console.log('Install will continue. Try running ' + 'grunt build:prod'.grey + ' after installation completes.'); + console.log('grunt build:prod command failed. Is the grunt-cli module installed? You can install using ' + 'npm install -g grunt grunt-cli'); + console.log('Install will continue. Try running ' + 'grunt build:prod' + ' after installation completes.'); return next(); } diff --git a/lib/application.js b/lib/application.js index 6a9549b5af..457554f2f2 100644 --- a/lib/application.js +++ b/lib/application.js @@ -26,6 +26,9 @@ var EventEmitter = require('events').EventEmitter, Mailer = require('./mailer').Mailer, configuration = require('./configuration'); +var request = require('request'); +var async = require('async'); +var chalk = require('chalk'); /** * Some defaults */ @@ -343,79 +346,175 @@ Origin.prototype.startServer = function () { } var app = this; - app.createServer(serverOptions, function (error, server) { - if (error) { - logger.log('fatal', 'error creating server', error); - return process.exit(1); - } - // use default port if configuration is not available - var port = app.configuration - ? app.configuration.getConfig('serverPort') - : DEFAULT_SERVER_PORT; + var installedBuilderVersion = '', installedFrameworkVersion = ''; + var latestBuilderTag = ''; + var latestFrameworkTag = ''; - app.server = server; + async.series([ + function(callback) { + // Check for the expected NodeJS version + if (process.version !== 'v0.10.33') { + console.log(chalk.red('Incorrect version of NodeJS detected')); + logger.log('warn', 'Incorrect version of NodeJS detected -- this code requires v0.10.33 to run correctly.'); + } - // Create a http server - var httpServer = require('http').createServer(server); - - app._httpServer = httpServer.listen(port, function(){ - // set up routes - app.router = router(app); + callback(); + }, + function(callback) { + // Read the current versions + var versionFile = JSON.parse(fs.readFileSync('version.json'), {encoding: 'utf8'}); + + if (versionFile) { + installedBuilderVersion = versionFile.adapt_authoring; + installedFrameworkVersion = versionFile.adapt_framework; + } + + callback(); + }, + function(callback) { + // Check the latest version of the project + request({ + headers: { + 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36' + }, + uri: 'https://api.github.com/repos/adaptlearning/adapt_authoring/tags', + method: 'GET' + }, function (error, response, body) { + if (!error && response.statusCode == 200) { + var tagInfo = JSON.parse(body); + + if (tagInfo) { + latestBuilderTag = tagInfo[0].name; + } + } + + callback(); + }); + }, + function(callback) { + // Check the latest version of the framework + request({ + headers: { + 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36' + }, + uri: 'https://api.github.com/repos/adaptlearning/adapt_framework/tags', + method: 'GET' + }, function (error, response, body) { + if (!error && response.statusCode == 200) { + var tagInfo = JSON.parse(body); + + if (tagInfo) { + latestFrameworkTag = tagInfo[0].name; + } + } + + callback(); + }); + }, + function(callback) { + var isUpdateAvailable = false; - // handle different server states - if (serverOptions.minimal) { - app.emit("minimalServerStarted", app.server); + if (installedBuilderVersion == latestBuilderTag) { + console.log(chalk.green('Adapt Builder %s'), installedBuilderVersion); } else { - app.emit("serverStarted", app.server); + console.log(chalk.yellow('You are currently running Adapt Builder %s - %s is now available'), installedBuilderVersion, latestBuilderTag); + isUpdateAvailable = true; } - var writeRebuildFile = function(courseFolder, callback) { - var OutputConstants = require('./outputmanager').Constants; - var buildFolder = path.join(courseFolder, OutputConstants.Folders.Build); - - fs.exists(buildFolder, function (exists) { - if (exists) { - // Write an empty lock file called .rebuild - logger.log('info', 'Writing build to ' + path.join(buildFolder, OutputConstants.Filenames.Rebuild)); - fs.writeFile(path.join(buildFolder, OutputConstants.Filenames.Rebuild), '', function (err) { - if (err) { - return callback(err); - } + if (installedFrameworkVersion == latestFrameworkTag) { + console.log(chalk.green('Adapt Framework %s'), installedFrameworkVersion); + } else { + console.log(chalk.yellow('The Adapt Framework being used is %s - %s is now available'), installedFrameworkVersion, latestFrameworkTag); + isUpdateAvailable = true; + } - return callback(null); - }); + if (isUpdateAvailable) { + console.log("Run " + chalk.bgRed('"node upgrade.js"') + " to update to the latest version"); + console.log(); + } + + callback(); + }, + function(callback) { + app.createServer(serverOptions, function (error, server) { + if (error) { + logger.log('fatal', 'error creating server', error); + return process.exit(1); + } + + // use default port if configuration is not available + var port = app.configuration + ? app.configuration.getConfig('serverPort') + : DEFAULT_SERVER_PORT; + + app.server = server; + + // Create a http server + var httpServer = require('http').createServer(server); + + app._httpServer = httpServer.listen(port, function(){ + // set up routes + app.router = router(app); + + // handle different server states + if (serverOptions.minimal) { + app.emit("minimalServerStarted", app.server); } else { - return callback(null); + app.emit("serverStarted", app.server); } - }); - }; - - app.on("rebuildCourse", function(tenantId, courseId) { - logger.log('info', 'Event:rebuildCourse triggered for Tenant: ' + tenantId + ' Course: ' + courseId); - // Not ideal, but there is a timing issue which prevente d - var OutputConstants = require('./outputmanager').Constants; - var courseFolder = path.join(configuration.tempDir, configuration.getConfig('masterTenantID'), OutputConstants.Folders.Framework, OutputConstants.Folders.AllCourses, tenantId, courseId); - - fs.exists(courseFolder, function(exists) { - if (exists) { - writeRebuildFile(courseFolder, function(err) { - if (err) { - logger.log('error', err); + + var writeRebuildFile = function(courseFolder, callback) { + var OutputConstants = require('./outputmanager').Constants; + var buildFolder = path.join(courseFolder, OutputConstants.Folders.Build); + + fs.exists(buildFolder, function (exists) { + if (exists) { + // Write an empty lock file called .rebuild + logger.log('info', 'Writing build to ' + path.join(buildFolder, OutputConstants.Filenames.Rebuild)); + fs.writeFile(path.join(buildFolder, OutputConstants.Filenames.Rebuild), '', function (err) { + if (err) { + return callback(err); + } + + return callback(null); + }); + } else { + return callback(null); + } + }); + }; + + app.on("rebuildCourse", function(tenantId, courseId) { + logger.log('info', 'Event:rebuildCourse triggered for Tenant: ' + tenantId + ' Course: ' + courseId); + // Not ideal, but there is a timing issue which prevente d + var OutputConstants = require('./outputmanager').Constants; + var courseFolder = path.join(configuration.tempDir, configuration.getConfig('masterTenantID'), OutputConstants.Folders.Framework, OutputConstants.Folders.AllCourses, tenantId, courseId); + + fs.exists(courseFolder, function(exists) { + if (exists) { + writeRebuildFile(courseFolder, function(err) { + if (err) { + logger.log('error', err); + } + + return; + }); } return; }); - } + }); + + logger.log('info', 'Server started listening on port ' + port); - return; + callback(); }); }); - - logger.log('info', 'Server started listening on port ' + port); - }); + } + ]); - }); + }; Origin.prototype.restartServer = function () { diff --git a/lib/frameworkhelper.js b/lib/frameworkhelper.js index e151fbeaf3..e15d9530a4 100644 --- a/lib/frameworkhelper.js +++ b/lib/frameworkhelper.js @@ -2,7 +2,6 @@ var path = require('path'), fs = require('fs'), util = require('util'), - colors = require('colors'), exec = require('child_process').exec; // errors @@ -18,7 +17,7 @@ var FRAMEWORK_DIR = 'adapt_framework', GIT_FRAMEWORK_CLONE_URL = 'https://github.com/adaptlearning/adapt_framework.git'; function flog(msg) { - console.log((' ' + msg).grey); + console.log(' ' + msg); } function cloneFramework (next) { diff --git a/lib/tenantmanager.js b/lib/tenantmanager.js index 0bdaeb5ad4..f3fdc82b71 100644 --- a/lib/tenantmanager.js +++ b/lib/tenantmanager.js @@ -366,6 +366,11 @@ exports = module.exports = { } database.getDatabase(function(err, db){ + if (err) { + logger.log('error', err); + return; + } + // delegate to db retrieve method db.retrieve('tenant', search, options, callback); }, configuration.getConfig('dbName')); diff --git a/package.json b/package.json index 0f1d9aead3..7161a9a8c5 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "bcrypt-nodejs": "0.0.3", "bower": "1.3", "casperjs": "1.1.0-beta3", - "colors": "1.0.3", + "chalk": "^1.0.0", "connect-mongo": "0.4.x", "consolidate": "0.10.0", "express": "3.4.0", @@ -65,9 +65,9 @@ "rimraf": "~2.2.5", "semver": "^2.3.1", "underscore": "~1.5.2", + "unzip": "0.1.8", "validator": "3.3.0", - "winston": "0.7.2", - "unzip": "0.1.8" + "winston": "0.7.2" }, "main": "index", "engines": { diff --git a/upgrade.js b/upgrade.js new file mode 100644 index 0000000000..fa3464043d --- /dev/null +++ b/upgrade.js @@ -0,0 +1,298 @@ +var prompt = require('prompt'); +var fs = require('fs'); +var request = require('request'); +var async = require('async'); +var exec = require('child_process').exec; + +// Constants +var DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'; + +// GLOBALS +var installedBuilderVersion = ''; +var latestBuilderTag = ''; +var installedFrameworkVersion = ''; +var latestFrameworkTag = ''; +var shouldUpdateBuilder = false; +var shouldUpdateFramework = false; +var versionFile = JSON.parse(fs.readFileSync('version.json'), {encoding: 'utf8'}); +var configFile = JSON.parse(fs.readFileSync('conf/config.json'), {encoding: 'utf8'}); + +var steps = [ + function(callback) { + + console.log('Checking versions'); + + if (versionFile) { + installedBuilderVersion = versionFile.adapt_authoring; + installedFrameworkVersion = versionFile.adapt_framework; + } + + console.log('Currently installed versions:\n- Adapt Builder: ' + installedBuilderVersion + '\n- Adapt Framework: ' + installedFrameworkVersion); + callback(); + + }, + function(callback) { + + console.log('Checking for Adapt Builder upgrades...'); + // Check the latest version of the project + request({ + headers: { + 'User-Agent' : DEFAULT_USER_AGENT + }, + uri: 'https://api.github.com/repos/adaptlearning/adapt_authoring/tags', + method: 'GET' + }, function (error, response, body) { + + if (!error && response.statusCode == 200) { + var tagInfo = JSON.parse(body); + + if (tagInfo) { + latestBuilderTag = tagInfo[0].name; + } + + callback(); + } + + }); + + }, + function(callback) { + + console.log('Checking for Adapt Framework upgrades...'); + // Check the latest version of the framework + request({ + headers: { + 'User-Agent' : DEFAULT_USER_AGENT + }, + uri: 'https://api.github.com/repos/adaptlearning/adapt_framework/tags', + method: 'GET' + }, function (error, response, body) { + if (!error && response.statusCode == 200) { + var tagInfo = JSON.parse(body); + + if (tagInfo) { + // For now - we should only worry about v1 tags of the framework + async.detectSeries(tagInfo, function(tag, callback) { + if (tag.name.split('.')[0] == 'v1') { + callback(tag); + } + }, function(latestVersion) { + + latestFrameworkTag = latestVersion.name; + callback(); + + }); + } + + } + }); + + }, + function(callback) { + // Check what needs upgrading + if (latestBuilderTag != installedBuilderVersion) { + shouldUpdateBuilder = true; + console.log('Update for Adapt Builder is available: ' + latestBuilderTag); + } + + if (latestFrameworkTag != installedFrameworkVersion) { + shouldUpdateFramework = true; + console.log('Update for Adapt Framework is available: ' + latestFrameworkTag); + } + + // If neither of the Builder or Framework need updating then quit the upgrading process + if (!shouldUpdateFramework && !shouldUpdateBuilder) { + console.log('No updates available at this time\n'); + process.exit(0); + } + + callback(); + + }, function(callback) { + // Upgrade Builder if we need to + if (shouldUpdateBuilder) { + + upgradeBuilder(latestBuilderTag, function(err) { + if (err) { + return callback(err); + } + + versionFile.adapt_authoring = latestBuilderTag; + callback(); + + }); + + } else { + callback(); + } + + }, function(callback) { + + // Upgrade Framework if we need to + if (shouldUpdateFramework) { + + upgradeFramework(latestFrameworkTag, function(err) { + if (err) { + return callback(err); + } + + versionFile.adapt_framework = latestFrameworkTag; + callback(); + + }); + + } else { + callback(); + } + + }, function(callback) { + + // After upgrading let's update the version.json to the latest version + fs.writeFile('version.json', JSON.stringify(versionFile, null, 4), function(err) { + if(err) { + callback(err); + } else { + console.log("Version file updated\n"); + callback(); + } + }); + + }, function(callback) { + // Left empty for any upgrade scripts - just remember to call the callback when done. + callback(); + } +]; + +prompt.start(); + +// Prompt the user to begin the install +console.log('This script will update the Adapt Builder (and/or Adapt Framework) to the latest released version. Would you like to continue?'); +prompt.get({ name: 'Y/n', type: 'string', default: 'Y' }, function (err, result) { + if (!/(Y|y)[es]*$/.test(result['Y/n'])) { + return exitUpgrade(); + } + + // run steps + async.series(steps, function (err, results) { + + if (err) { + console.log('ERROR: ', err); + return exitUpgrade(1, 'Upgrade was unsuccessful. Please check the console output.'); + } + + exitUpgrade(0, 'Great work! Your Adapt Builder is now updated.'); + }); +}); + +// This upgrades the Builder +function upgradeBuilder(tagName, callback) { + + console.log('Upgrading the Adapt Builder...please hold on!'); + var child = exec('git fetch origin', { + stdio: [0, 'pipe', 'pipe'] + }); + + child.stdout.on('data', function(err) { + console.log(err); + }); + child.stderr.on('data', function(err) { + console.log(err); + }); + + child.on('exit', function (error, stdout, stderr) { + if (error) { + return console.log('ERROR: ' + error); + } + + console.log("Fetch from GitHub was successful."); + console.log("Pulling latest changes..."); + + var secondChild = exec('git reset --hard ' + tagName, { + stdio: [0, 'pipe', 'pipe'] + }); + + secondChild.stdout.on('data', function(err) { + console.log(err); + }); + + secondChild.stderr.on('data', function(err) { + console.log(err); + }); + + secondChild.on('exit', function (error, stdout, stderr) { + if (error) { + return console.log('ERROR: ' + error); + } + + console.log("Builder has been updated.\n"); + callback(); + + }); + + }); +} + +// This upgrades the Framework +function upgradeFramework(tagName, callback) { + console.log('Upgrading the Adapt Framework...please hold on!'); + + var child = exec('git fetch origin', { + cwd: 'temp/' + configFile.masterTenantID + '/adapt_framework', + stdio: [0, 'pipe', 'pipe'] + }); + + child.stdout.on('data', function(err) { + console.log(err); + }); + + child.stderr.on('data', function(err) { + console.log(err); + }); + + child.on('exit', function (error, stdout, stderr) { + if (error) { + return console.log('ERROR: ' + error); + } + + console.log("Fetch from GitHub was successful."); + console.log("Pulling latest changes..."); + + var secondChild = exec('git reset --hard ' + tagName, { + cwd: 'temp/' + configFile.masterTenantID + '/adapt_framework', + stdio: [0, 'pipe', 'pipe'] + }); + + secondChild.stdout.on('data', function(err) { + console.log(err); + }); + + secondChild.stderr.on('data', function(err) { + console.log(err); + }); + + secondChild.on('exit', function (error, stdout, stderr) { + if (error) { + return console.log('ERROR: ' + error); + } + + console.log("Framework has been updated.\n"); + callback(); + + }); + + }); +} + +/** + * Exits the install with some cleanup, should there be an error + * + * @param {int} code + * @param {string} msg + */ + +function exitUpgrade (code, msg) { + code = code || 0; + msg = msg || 'Bye!'; + console.log(msg); + process.exit(code); +} \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 0000000000..fb064357f1 --- /dev/null +++ b/version.json @@ -0,0 +1,4 @@ +{ + "adapt_authoring": "v0.1.0", + "adapt_framework": "v1.1.1" +} \ No newline at end of file