From dcf1d2bd4c6d42d987be01ac74f9a5cd69e21b33 Mon Sep 17 00:00:00 2001 From: cliftonc Date: Sun, 8 Apr 2012 11:23:26 +0100 Subject: [PATCH] Fixed broken cluster mode, now uses v0.6+ cluster and removed Hook.IO (need another way of interprocess comms) --- app.js | 48 +++--- bin/calipso | 6 +- cluster.js | 130 +++++++++++++++ lib/Config.js | 2 +- lib/Event.js | 72 ++------- lib/Hook.js | 35 ---- lib/Theme.js | 92 ++++++----- lib/calipso.js | 153 +++++++++--------- lib/conf/default.json | 11 +- modules/core/content/content.js | 26 +-- modules/core/contentTypes/contentTypes.js | 18 +-- .../core/contentVersions/contentVersions.js | 14 +- modules/core/permissions/permissions.js | 26 ++- modules/core/scheduler/scheduler.js | 16 +- modules/core/tagcloud/tagcloud.js | 6 +- modules/core/taxonomy/taxonomy.js | 6 +- modules/core/user/user.js | 32 ++-- package.json | 2 - themes/core/cleanslate/templates/admin.html | 2 +- 19 files changed, 367 insertions(+), 330 deletions(-) create mode 100644 cluster.js delete mode 100644 lib/Hook.js diff --git a/app.js b/app.js index da0daa562..00c253662 100644 --- a/app.js +++ b/app.js @@ -25,21 +25,15 @@ var rootpath = process.cwd() + '/', // Local App Variables var path = rootpath, - theme = 'default', - port = process.env.PORT || 3000; + theme = 'default', + port = process.env.PORT || 3000; /** * Catch All exception handler */ -process.on('uncaughtException', function (err) { - console.log('Uncaught exception: ' + err + err.stack); -}); - - -/** - * Placeholder for application - */ -var app, exports; +//process.on('uncaughtException', function (err) { +// console.log('Uncaught exception: ' + err + err.stack); +//}); /** * App settings and middleware @@ -48,6 +42,16 @@ var app, exports; */ function bootApplication(next) { + // Create our express instance, export for later reference + var app = express.createServer(); + app.path = path; + app.isCluster = false; + + // Load configuration + var Config = require(path + "/lib/Config").Config; + app.config = new Config(); + app.config.init(); + app.use(express.methodOverride()); app.use(express.cookieParser()); app.use(express.responseTime()); @@ -110,31 +114,17 @@ function bootApplication(next) { app.use(translate.translate(app.config.get('i18n:language'), app.config.get('i18n:languages'), app.config.get('i18n:additive'))); // Core calipso router - app.use(calipso.calipsoRouter(next)); + app.use(calipso.calipsoRouter(app, function() { next(app) })); } /** * Initial bootstrapping */ -exports.boot = function (next,cluster) { - - //Create our express instance, export for later reference - app = exports.app = express.createServer(); - app.path = path; - app.isCluster = cluster; +exports.boot = function (cluster, next) { - // Load configuration - var Config = require(path + "/lib/Config").Config; - app.config = new Config(); - app.config.init(); - - // Load application configuration - // theme = app.config.get('themes:front'); // Bootstrap application - bootApplication(function () { - next(app); - }); + bootApplication(next); }; @@ -144,7 +134,7 @@ if (!module.parent) { logo.print(); - exports.boot(function (app) { + exports.boot(false, function (app) { if (app) { app.listen(port); diff --git a/bin/calipso b/bin/calipso index f57587029..30a5cffc3 100755 --- a/bin/calipso +++ b/bin/calipso @@ -86,7 +86,7 @@ function runLauncher(appLauncher) { break; case 'modules': process.chdir(path); - require(path + '/app').boot(function(app) { + require(path + '/app').boot(false, function(app) { var modules = require(calipsoPath + '/lib/cli/Modules') modules.moduleRouter(path,appLauncher.script.params,true,function(err) { if(err) { @@ -98,7 +98,7 @@ function runLauncher(appLauncher) { break; case 'themes': process.chdir(path); - require(path + '/app').boot(function(app) { + require(path + '/app').boot(false, function(app) { var themes = require(calipsoPath + '/lib/cli/Themes') themes.themeRouter(path,appLauncher.script.params,true,function(err) { if(err) { @@ -171,7 +171,7 @@ function runServer(port) { // Ensure we run in the local folder of the application process.chdir(path); - require(path + '/app').boot(function(app) { + require(path + '/app').boot(false, function(app) { app.listen(port); console.log("Calipso server listening on port: ".green + app.address().port.toString().white.bold); console.log("Calipso configured for ".green + (global.process.env.NODE_ENV || 'development').white.bold + " environment\r\n".green); diff --git a/cluster.js b/cluster.js new file mode 100644 index 000000000..dedf92496 --- /dev/null +++ b/cluster.js @@ -0,0 +1,130 @@ +/** + * Master server process + * Initialises a number of instances of the app, based on the number of CPU's it detects + * is available. + * + * First arg is the port + * Second arg is the num worker threads; + * + * e.g. node server 3000 8 + * + */ +// Dependencies +var rootpath = process.cwd() + '/', + cluster = require('cluster'), + path = require('path'), + logo = require(path.join(rootpath, 'logo')), + colors = require('colors'), + port = process.env.PORT || 3000, + restarts = 0, + totalWorkers = 0, + runningWorkers = 0; + +var argv = processArgs(); + +launchServer(); + +/** + * Launch server instance, initially master, then each worker instance is forked. + * All instances share same config. + */ +function launchServer() { + + // Check if we are the master process + if (cluster.isMaster) { + + //require('./app').boot(function (app) { + + + // Load configuration + var Config = require(rootpath + "lib/Config").Config, + config = new Config(); + config.init(); + + // Print the logo + logo.print(); + + // Set the number of workers + totalWorkers = config.get('server:cluster:workers') || argv.c; + + // Fork workers based on num cpus + for (var i = 0; i < totalWorkers; i++) { + forkWorker(); + } + + // Log worker death + // TODO : Auto restart with number of retries + cluster.on('death', function(worker) { + + console.error('worker ' + worker.pid + ' died ...'); + + // Manage restarting of workers + if(config.get('server:cluster:restartWorkers')) { + if(restarts > config.get('server:cluster:maximumRestarts')) { + console.error('Maximum number of restarts reached, not restarting this worker.'.red); + } else { + restarts++; + forkWorker(); + } + } + + }); + + //}); + + } else { + + // We are a child worker, so bootstrap the app. + require(rootpath + 'app').boot(true, function (app) { + + //logger.info("Worker [" + argv.m.cyan + "] with pid " + (process.pid + "").grey + " online."); + app.listen(port); + + process.send({ cmd: 'workerStarted', pid: process.pid, port: port }); + + }); + + } + +} + +/** + * Helper function to fork a worker, we need to reset the counter in the master thread + * hence the messaging back, also deal with messaging around job management from worker threads. + */ +function forkWorker() { + + var worker = cluster.fork(); + + worker.on('message', function(msg) { + + if (msg.cmd) { + + if(msg.cmd == 'workerStarted') { + + runningWorkers++; + + if(runningWorkers === totalWorkers) { + console.log("Calipso configured for: ".green + (global.process.env.NODE_ENV || 'development') + " environment.".green); + console.log("Calipso server running ".green + runningWorkers + " workers, listening on port: ".green + port); + } + + } + + } + + }); + +} + +/** + * Process command line arguments using optimist + */ +function processArgs() { + return require('optimist') + .usage('Launch Calipso in Clustered Mode\nUsage: $0') + .describe('c', 'Number of CPUs') + .alias('c', 'cpu') + .default('c', require('os').cpus().length) + .argv; +} diff --git a/lib/Config.js b/lib/Config.js index 12112d7ed..c74bbb732 100644 --- a/lib/Config.js +++ b/lib/Config.js @@ -29,7 +29,7 @@ function Config(options) { Config.prototype.init = function() { this.nconf = require('nconf'); this.load(function(err) { - console.log(err); + if(err) console.error(err); }); } diff --git a/lib/Event.js b/lib/Event.js index 74e92a1bd..642024787 100644 --- a/lib/Event.js +++ b/lib/Event.js @@ -7,8 +7,6 @@ * to provide the ability for module dependencies to be managed, as well as enable modules * to ensure that they run after all other modules have emitted certain events (e.g. menu rendering). * - * This library additionally introduces the Calipso Hook.io Wrapper, allowing certain calipso events to be - * broadcast to a hook.io cloud. * */ @@ -20,8 +18,7 @@ var rootpath = process.cwd() + '/', util = require('util'), events = require('events'), app = require(path.join(rootpath, 'app')), - calipso = require(path.join(rootpath, 'lib/calipso')), - CalipsoHook = require(path.join(rootpath, 'lib/Hook')).CalipsoHook; + calipso = require(path.join(rootpath, 'lib/calipso')); exports = module.exports = { CalipsoEventEmitter: CalipsoEventEmitter, @@ -50,16 +47,6 @@ function CalipsoEventEmitter(options) { // Initialise options this.options = options || {}; - // Create a hook.io emitter - defaults - var hookOptions = this.options.hook || {}; - - // Override hook options with PID for class, clarifies logging - hookOptions.name = hookOptions.name + "-" + process.pid; - - this.hook = new CalipsoHook(hookOptions); - this.hook.setMaxListeners(calipso.config.get('server:hookio:maxListeners')); - this.hook.start(); - // Create an emitter to drive events this.emitter = new events.EventEmitter(); this.emitter.setMaxListeners(calipso.config.get('server:events:maxListeners')); @@ -76,13 +63,9 @@ function CalipsoEventEmitter(options) { self.emitter.removeAllListeners("PRE_" + event); self.emitter.removeAllListeners("POST_" + event); - self.hook.removeAllListeners("*::calipso::PRE_" + event); - self.hook.removeAllListeners("*::calipso::POST_" + event); - if(self.events[event].custom) { for(var key in self.events[event].custom) { self.emitter.removeAllListeners(key + "_" + event); - self.hook.removeAllListeners("calipso::" + key + "_" + event); }; } @@ -95,7 +78,7 @@ function CalipsoEventEmitter(options) { // Wrapper for event emitter, enable turn on / off this.addEvent = function(event, options) { - var options = calipso.lib._.extend({enabled:true, hookio:false},options); + var options = calipso.lib._.extend({enabled:true},options); this.events[event] = options; // Enable tracking of attached listeners for debugging purposes this.events[event].preListeners = {'#':0}; @@ -108,43 +91,32 @@ function CalipsoEventEmitter(options) { // Register a pre listener this.pre = function(event,listener,fn) { - if(this.events[event].hookio && calipso.app.isCluster) { - this.hook.on('*::calipso::' + pre_prefix + event, fn); - } else { - self.emitter.on(pre_prefix + event, fn); - } + + self.emitter.on(pre_prefix + event, fn); this.events[event].preListeners[listener] = this.events[event].preListeners[listener] || []; - this.events[event].preListeners[listener].push({name:fn.name, hookio:this.events[event].hookio}); + this.events[event].preListeners[listener].push({name:fn.name}); this.events[event].preListeners['#'] += 1; } // Register a post listener this.post = function(event,listener,fn) { - if(this.events[event].hookio && calipso.app.isCluster) { - this.hook.on('*::calipso::' + post_prefix + event, fn); - } else { - self.emitter.on(post_prefix + event, fn); - } + self.emitter.on(post_prefix + event, fn); this.events[event].postListeners[listener] = this.events[event].postListeners[listener] || []; - this.events[event].postListeners[listener].push({name:fn.name, hookio:this.events[event].hookio}); + this.events[event].postListeners[listener].push({name:fn.name}); this.events[event].postListeners['#'] += 1; } // Register a custom event listener this.custom = function(event,key,listener,fn) { - if(this.events[event].hookio && calipso.app.isCluster) { - this.hook.on('*::calipso::' + event + '::' + key, fn); - } else { - self.emitter.on(event, fn); - } + self.emitter.on(event, fn); // Register under key this.events[event].custom[key] = this.events[event].custom[key] || {customListeners: {'#':0}}; // Register this.events[event].custom[key].customListeners[listener] = this.events[event].custom[key].customListeners[listener] || []; - this.events[event].custom[key].customListeners[listener].push({name:fn.name, hookio:this.events[event].hookio}); + this.events[event].custom[key].customListeners[listener].push({name:fn.name}); this.events[event].custom[key].customListeners['#'] += 1; } @@ -160,13 +132,7 @@ function CalipsoEventEmitter(options) { } if(this.events[event] && this.events[event].enabled) { - if(this.events[event].hookio && calipso.app.isCluster) { - this.hook.emit('calipso::' + pre_prefix + event, data); - cb(data); - } else { - self.emitter.emit(pre_prefix + event, pre_prefix + event, data, cb); - } - + self.emitter.emit(pre_prefix + event, pre_prefix + event, data, cb); } } @@ -182,13 +148,7 @@ function CalipsoEventEmitter(options) { } if(this.events[event] && this.events[event].enabled) { - if(this.events[event].hookio && calipso.app.isCluster) { - this.hook.emit('calipso::' + post_prefix + event, data); - cb(data); - } else { - self.emitter.emit(post_prefix + event, post_prefix + event, data, cb); - } - + self.emitter.emit(post_prefix + event, post_prefix + event, data, cb); } } @@ -205,13 +165,9 @@ function CalipsoEventEmitter(options) { var cb = function() {}; } - if(this.events[event].hookio && calipso.app.isCluster) { - this.hook.emit('calipso::' + event + '::' + key, data); - cb(data); - } else { - self.emitter.emit(event, key, data, cb); - } - + + self.emitter.emit(event, key, data, cb); + } else { next(data); } diff --git a/lib/Hook.js b/lib/Hook.js deleted file mode 100644 index 72d9b60cd..000000000 --- a/lib/Hook.js +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Calipso Hook.IO Library - * Copyright(c) 2011 Clifton Cunningham - * MIT Licensed - * - * This library additionally introduces the Calipso Hook.io Wrapper, allowing any calipso event to be - * broadcast to a hook.io cloud. - * - */ - -/** - * Includes - */ -var util = require('util'), - Hook = require('hook.io').Hook; - -exports = module.exports = { - CalipsoHook: CalipsoHook -}; - -/** - * Calipso hook.io emitter - */ -function CalipsoHook(options) { - - var self = this; - Hook.call(self, options); - self.on('hook::ready', function(){ - // ? - }); - -} - -util.inherits(CalipsoHook, Hook); - diff --git a/lib/Theme.js b/lib/Theme.js index 7218428a9..6a772e847 100644 --- a/lib/Theme.js +++ b/lib/Theme.js @@ -458,27 +458,24 @@ function cacheTheme(themeConfig, themePath, next) { }); calipso.lib._.each(errorCodeTemplates, function(filename){ - templates.push({name: filename.match(/^\d{3}/), templatePath: calipso.lib.path.join("templates","error",filename)}); + templates.push({name: filename.match(/^\d{3}/)[0], templatePath: calipso.lib.path.join("templates","error",filename)}); }); - // Now load them all into the cache - calipso.lib.step( - function loadTemplates() { - var group = this.group(); - templates.forEach(function(template) { - loadTemplate(templateCache, template, themePath, group()); - }); - }, - function done(err) { - if(err) { + var templateIterator = function(templateName, cb) { + loadTemplate(templateCache, templateName, themePath, cb); + } + + calipso.lib.async.map(templates, templateIterator, function(err, result) { + + if(err) { // May not be a problem as missing templates default to default calipso.error("Error loading templates, msg: " + err.message + ", stack: " + err.stack); next(err); } else { - next(null,templateCache); + next(null, templateCache); } - } - ); + }) + } } @@ -523,49 +520,55 @@ function loadTemplate(templateCache, template, themePath, next) { var templateExtension = template.templatePath.match(/([^\.]+)$/)[0]; var templateFnPath = calipso.lib.path.join(themePath,template.templatePath.replace("." + templateExtension, ".js")); - path.exists(templatePath,function(exists) { + path.exists(templatePath, function(exists) { if(exists) { - fs.readFile(templatePath, 'utf8', function(err,data) { + var templateData = ''; - if(!err) { + try { + templateData = fs.readFileSync(templatePath, 'utf8'); + } catch(err) { + calipso.error('Failed to open template ' + templatePath + ' ...'); + } - if(calipso.config.get('performance:watchFiles')) { + if(calipso.config.get('performance:watchFiles')) { - try { - fs.unwatchFile(templatePath); - fs.watchFile(templatePath, { persistent: true, interval: 200}, function(curr,prev) { - loadTemplate(templateCache, template, themePath, function() { - calipso.silly("Template " + templatePath + " reloaded ..."); - }); + try { + fs.unwatchFile(templatePath); + fs.watchFile(templatePath, { persistent: true, interval: 200}, function(curr,prev) { + loadTemplate(templateCache, template, themePath, function() { + calipso.silly("Template " + templatePath + " reloaded ..."); }); + }); + } catch(ex) { + calipso.error('Failed to watch template ' + templatePath + ' ...'); + } + + } + + // Precompile the view into our cache + templateCache[template.name].template = compileTemplate(templateData, templatePath, templateExtension); + + // See if we have a template fn + if(path.existsSync(templateFnPath)) { + + if(exists) { + try { + templateCache[template.name].fn = require(templateFnPath); } catch(ex) { - next(ex); + calipso.error(ex); } - } - // Precompile the view into our cache - templateCache[template.name].template = compileTemplate(data,templatePath,templateExtension); - path.exists(templateFnPath, function(exists) { - if(exists) { - try { - templateCache[template.name].fn = require(templateFnPath); - } catch(ex) { - next(ex); - return; - } - } - next(null,templateCache); - }); - } else { - next(err); - } - }); + }; + return next(null, template); + } else { + next(new Error('Path does not exist: ' + templatePath)); + } }); @@ -579,7 +582,7 @@ function loadTemplate(templateCache, template, themePath, next) { */ function compileTemplate(data,templatePath,templateExtension) { - var compiledTemplate; + var compiledTemplate = function() {}; var options = {filename:templatePath}; // If we get html, replace with ejs @@ -599,6 +602,7 @@ function compileTemplate(data,templatePath,templateExtension) { } catch(ex) { calipso.error("Error compiling template : " + templatePath + ", message: " + ex.message); } + return compiledTemplate; } diff --git a/lib/calipso.js b/lib/calipso.js index 6950fe535..501f53787 100644 --- a/lib/calipso.js +++ b/lib/calipso.js @@ -44,7 +44,7 @@ var app, express: require('express'), step: require('step'), util: require('util'), - mongoose: require('mongoose'), + mongoose: require('mongoose'), url: require('url'), ejs: require('ejs'), pager: require(path.join(rootpath, 'utils/pager')), @@ -91,14 +91,10 @@ var app, * Returns a connect middleware function that manages the roucting * of requests to modules. */ - calipsoRouter: function(initCallback) { + calipsoRouter: function(app, initCallback) { - // Set app and config references - var app = require(path.join(rootpath, 'app')).app; - - app.calipso = app; calipso.app = app; - + // Load the calipso package.json into about loadAbout(app, rootpath, 'package.json'); @@ -107,20 +103,13 @@ var app, // Configure the cache calipso.cache = require('./Cache').Cache({ttl:calipso.config.get('performance:cache:ttl')}); - // Store our references and options + // Store the callback function for later calipso.initCallback = function() { initCallback(); }; // Create our calipso event emitter - calipso.e = new calipso.event.CalipsoEventEmitter({ - hook:{ - name:calipso.config.get('server:hookio:name'), - 'hook-host':calipso.config.get('server:hookio:host'), - 'hook-port': calipso.config.get('server:hookio:port'), - debug: calipso.config.get('server:hookio:debug') - } - }); + calipso.e = new calipso.event.CalipsoEventEmitter(); // Load configuration initialiseCalipso(); @@ -194,7 +183,7 @@ function initialiseCalipso(reloadConfig) { calipso.logging.configureLogging(); // Check / Connect Mongo - mongoConnect(calipso.config.get('database:uri'),false,function(err,connected) { + mongoConnect(calipso.config.get('database:uri'), false, function(err, connected) { if(err) { console.log("There was an error connecting to the database: " + err.message); @@ -295,7 +284,7 @@ function routeModule(req, res, moduleName, depends, last, next) { // Gracefully deal with errors if(err) { res.statusCode = 500; - console.log(err.message); + calipso.error(err.message); res.errorMessage = "Module " + moduleName + ", error: " + err.message + err.stack; } @@ -352,8 +341,6 @@ function doLastModules(req, res, next) { } } - - // Execute their routing functions calipso.lib.step( function doLastModules() { @@ -413,7 +400,6 @@ function doResponse(req, res, next) { } else { - calipso.silly("Rendering output ..."); res.setHeader('Content-Type', 'text/html'); // Who am I? res.setHeader('X-Powered-By','Calipso'); @@ -478,6 +464,7 @@ function initModules() { */ function initModule(module, depends) { + // If the module has no dependencies, kick start it if(depends || !calipso.modules[module].fn.depends) { @@ -485,7 +472,7 @@ function initModule(module, depends) { calipso.modules[module].event.init_start(); // Next run any init functions - calipso.modules[module].fn.init(calipso.modules[module], calipso.app,function() { + calipso.modules[module].fn.init(calipso.modules[module], calipso.app, function(err) { // Init finish event calipso.modules[module].inited = true; @@ -559,34 +546,41 @@ function loadModules() { if(type != "README" && type != '.DS_Store') { // Ignore the readme file and .DS_Store file for Macs - calipso.lib.fs.readdirSync(__dirname + '/../modules/' + type).forEach(function(name){ + calipso.lib.fs.readdirSync(__dirname + '/../modules/' + type).forEach(function(moduleFolderName){ - if(name != "README" && name != '.DS_Store') { // Ignore the readme file and .DS_Store file for Macs + if(moduleFolderName != "README" && moduleFolderName != '.DS_Store') { // Ignore the readme file and .DS_Store file for Macs - var enabled = configuredModules[name] && configuredModules[name].enabled; - - var modulePath = 'modules/' + type + '/' + name; - - // Create the module object - calipso.modules[name] = { - name: name, - library: name, + // Create the basic module + var modulePath = 'modules/' + type + '/' + moduleFolderName; + var module = { + name: moduleFolderName, + folder: moduleFolderName, + library: moduleFolderName, type: type, path: modulePath, - enabled: enabled, + enabled: false, inited: false }; - // Load the about info from package.json - loadAbout(calipso.modules[name], modulePath, 'package.json'); + // Add about info to it + loadAbout(module, modulePath, 'package.json'); + + // Set the module name to what is in the package.json, default to folder name + module.name = module.about.name ? module.about.name : moduleFoldername; + + // Now set the module + calipso.modules[module.name] = module; + + // Set if it is enabled or not + module.enabled = configuredModules[module.name] ? configuredModules[module.name].enabled : false; - if(enabled) { + if(module.enabled) { // Load the module itself via require - requireModule(calipso.modules[name], modulePath); + requireModule(calipso.modules[module.name], modulePath); // Load the templates (factored out so it can be recalled by watcher) - loadModuleTemplates(calipso.modules[name], modulePath + '/templates'); + loadModuleTemplates(calipso.modules[module.name], modulePath + '/templates'); } @@ -814,7 +808,7 @@ function checkRouted(req,res,moduleName,notifyModuleName) { function requireModule(module, modulePath, reload) { var fs = calipso.lib.fs; - var moduleFile = path.join(rootpath, modulePath + '/' + module.library); + var moduleFile = path.join(rootpath, modulePath + '/' + module.name); try { @@ -829,8 +823,10 @@ function requireModule(module, modulePath, reload) { module.fn.routes = module.fn.routes || []; } catch(ex) { + calipso.error("Module " + module.name + " has been disabled because " + ex.message); calipso.modules[module.name].enabled = false; + } } @@ -957,10 +953,11 @@ function configureTheme(next, overrideTheme) { var defaultTheme = calipso.config.get("theme:default"); var themeName = overrideTheme ? overrideTheme : calipso.config.get('theme:front'); var theme = calipso.themes[themeName]; // Reference to theme.json + var Theme = require('./Theme'); if(theme) { - require('./Theme').Theme(theme, function(err, loadedTheme) { + Theme.Theme(theme, function(err, loadedTheme) { calipso.theme = loadedTheme; @@ -1018,13 +1015,12 @@ function configureTheme(next, overrideTheme) { } -} - +} /** * Check that the mongodb instance specified in the configuration is valid. */ -function mongoConnect(dbUri,checkInstalling,next) { +function mongoConnect(dbUri, checkInstalling, next) { // Test the mongodb configuration var isInstalled = calipso.config.get('installed'); @@ -1038,8 +1034,8 @@ function mongoConnect(dbUri,checkInstalling,next) { // Check we are installing ... if(checkInstalling) { - var db = mongoose.createConnection(dbUri,function(err) { - next(err,false); + var db = mongoose.createConnection(dbUri, function(err) { + next(err, false); }); return; } @@ -1049,41 +1045,46 @@ function mongoConnect(dbUri,checkInstalling,next) { // Always disconnect first just in case any left overs from installation mongoose.disconnect(function() { - mongoose.connect(dbUri,function(err) { - if (err) { - // Add additional detail to know errors - switch (err.code) { - case "ECONNREFUSED": - calipso.error("Unable to connect to the specified database: ".red + dbUri); - break; - default: - calipso.error("Fatal unknown error: ".magenta + err); + // TODO - what the hell is going on with mongoose? + calipso.db = mongoose.createConnection(dbUri, function(err) { + + if (err) { + + // Add additional detail to know errors + switch (err.code) { + case "ECONNREFUSED": + calipso.error("Unable to connect to the specified database: ".red + dbUri); + break; + default: + calipso.error("Fatal unknown error: ".magenta + err); + } + + mongoose.disconnect(function() { + next(err); + }); + } - mongoose.disconnect(function() { - next(err); - }); - } else { - calipso.silly("Database connection to " + dbUri + " was successful."); - - // Replace the inmemory session with mongodb backed one - var foundMiddleware = false, mw; - calipso.app.stack.forEach(function(middleware,key) { - if(middleware.handle.tag === 'session') { - foundMiddleware = true; - mw = calipso.lib.express.session({ secret: calipso.config.get('session:secret') , store: mongoStore({ url: calipso.config.get('database:uri') }) }); - mw.tag = 'session' - calipso.app.stack[key].handle = mw; - } - }); - - if(!foundMiddleware) { - return next(new Error("Unable to load the MongoDB backed session, please check your session and db configuration"),false); + + }); + + calipso.silly("Database connection to " + dbUri + " was successful."); + + // Replace the inmemory session with mongodb backed one + var foundMiddleware = false, mw; + calipso.app.stack.forEach(function(middleware,key) { + if(middleware.handle.tag === 'session') { + foundMiddleware = true; + mw = calipso.lib.express.session({ secret: calipso.config.get('session:secret') , store: mongoStore({ url: calipso.config.get('database:uri') }) }); + mw.tag = 'session' + calipso.app.stack[key].handle = mw; } + }); - return next(null,true); + if(!foundMiddleware) { + return next(new Error("Unable to load the MongoDB backed session, please check your session and db configuration"),false); + } - } - }); + return next(null, true); }); diff --git a/lib/conf/default.json b/lib/conf/default.json index c320e3052..6624c1705 100644 --- a/lib/conf/default.json +++ b/lib/conf/default.json @@ -9,13 +9,10 @@ "url":"http://localhost:3000", "modulePath":"./modules", "themePath":"./themes", - "hookio":{ - "name":"calipso-hook", - "host":"127.0.0.1", - "socket":"", - "port":9001, - "debug":false, - "maxListeners":500 + "cluster": { + "workers": 0, + "restartWorkers": true, + "maximumRestarts": 1 }, "events":{ "maxListeners":500 diff --git a/modules/core/content/content.js b/modules/core/content/content.js index aec8fab16..020038f8d 100644 --- a/modules/core/content/content.js +++ b/modules/core/content/content.js @@ -119,7 +119,7 @@ function init(module,app,next) { next(); }); - calipso.lib.mongoose.model('Content', Content); + calipso.db.model('Content', Content); next(); @@ -155,7 +155,7 @@ function getContent(req, options, next) { options = calipso.lib._.extend(defaults, options); } - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); Content.findOne({alias:options.alias},function (err, c) { @@ -261,8 +261,8 @@ function createContent(req,res,template,block,next) { if(form) { - var Content = calipso.lib.mongoose.model('Content'); - var ContentType = calipso.lib.mongoose.model('ContentType'); + var Content = calipso.db.model('Content'); + var ContentType = calipso.db.model('ContentType'); var c = new Content(form.content); @@ -362,7 +362,7 @@ function getForm(req,action,title,contentType,next) { form.title = title; // Get content type - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); ContentType.findOne({contentType:contentType}, function(err, ct) { @@ -492,7 +492,7 @@ function createContentFormByType(req,res,template,block,next) { */ function editContentForm(req,res,template,block,next) { - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); var id = req.moduleParams.id; var item; @@ -557,8 +557,8 @@ function updateContent(req,res,template,block,next) { if(form) { - var Content = calipso.lib.mongoose.model('Content'); - var ContentType = calipso.lib.mongoose.model('ContentType'); + var Content = calipso.db.model('Content'); + var ContentType = calipso.db.model('ContentType'); var returnTo = form.returnTo ? form.returnTo : ""; var id = req.moduleParams.id; @@ -659,7 +659,7 @@ function showAliasedContent(req, res, template, block, next) { // Check type if(calipso.lib._.any(allowedFormats,function(value) { return value === format; })) { - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); Content.findOne({alias:alias},function (err, content) { @@ -702,7 +702,7 @@ function showAliasedContent(req, res, template, block, next) { */ function showContentByID(req,res,template,block,next) { - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); var id = req.moduleParams.id; var format = req.moduleParams.format ? req.moduleParams.format : 'html'; @@ -797,7 +797,7 @@ function showContent(req,res,template,block,next,err,content,format) { function listContent(req,res,template,block,next) { // Re-retrieve our object - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); var cPerm = calipso.permissions.hasPermission("content:create"), vPerm = calipso.permissions.hasPermission("content:view"); @@ -868,7 +868,7 @@ function contentLink(req,content) { */ function getContentList(query,out,next) { - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); var pager = out.hasOwnProperty('pager') ? out.pager : true; // If pager is enabled, ignore any override in from @@ -968,7 +968,7 @@ function getContentList(query,out,next) { */ function deleteContent(req,res,template,block,next) { - var Content = calipso.lib.mongoose.model('Content'); + var Content = calipso.db.model('Content'); var id = req.moduleParams.id; Content.findById(id, function(err, c) { diff --git a/modules/core/contentTypes/contentTypes.js b/modules/core/contentTypes/contentTypes.js index 447ea7646..09baa18d4 100644 --- a/modules/core/contentTypes/contentTypes.js +++ b/modules/core/contentTypes/contentTypes.js @@ -89,7 +89,7 @@ function init(module,app,next) { listTemplate:{type: String, "default": ''}, }); - calipso.lib.mongoose.model('ContentType', ContentType); + calipso.db.model('ContentType', ContentType); // Cache the content types in the calipso.data object if(app.config.get('installed')) { @@ -108,7 +108,7 @@ function init(module,app,next) { function install(next) { // Create the default content types - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); calipso.lib.step( function createDefaults() { @@ -177,7 +177,7 @@ function createContentType(req,res,template,block,next) { if(form) { - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); var c = new ContentType(form.contentType); c.ispublic = form.contentType.contentType.ispublic === "Yes" ? true : false; @@ -232,7 +232,7 @@ function createContentTypeForm(req,res,template,block,next) { */ function editContentTypeForm(req,res,template,block,next) { - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); var id = req.moduleParams.id; var item; @@ -278,7 +278,7 @@ function updateContentType(req,res,template,block,next) { if(form) { - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); var id = req.moduleParams.id; ContentType.findById(id, function(err, c) { @@ -324,7 +324,7 @@ function showContentType(req,res,template,block,next) { var item; - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); var id = req.moduleParams.id; format = req.moduleParams.format || 'html'; @@ -378,7 +378,7 @@ function showContentType(req,res,template,block,next) { function listContentType(req,res,template,block,next) { // Re-retrieve our object - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); res.menu.adminToolbar.addMenuItem(req, {name:'New Type',path:'new',url:'/content/type/new',description:'Create content type ...',permit:calipso.permissions.hasPermission("admin:content:type:create")}); @@ -421,7 +421,7 @@ function listContentType(req,res,template,block,next) { */ function deleteContentType(req,res,template,block,next) { - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); var id = req.moduleParams.id; ContentType.findById(id, function(err, c) { @@ -486,7 +486,7 @@ function compileTemplates(event, contentType, next) { */ function storeContentTypes(event,contentType,next) { - var ContentType = calipso.lib.mongoose.model('ContentType'); + var ContentType = calipso.db.model('ContentType'); delete calipso.data.contentTypes; calipso.data.contentTypes = []; diff --git a/modules/core/contentVersions/contentVersions.js b/modules/core/contentVersions/contentVersions.js index bac8a11a3..e0d69250b 100644 --- a/modules/core/contentVersions/contentVersions.js +++ b/modules/core/contentVersions/contentVersions.js @@ -74,7 +74,7 @@ function init(module,app,next) { // All other properties are dynamically mapped, hence use of .set / .get }); - calipso.lib.mongoose.model('ContentVersion', ContentVersion); + calipso.db.model('ContentVersion', ContentVersion); // Version event listeners calipso.e.post('CONTENT_CREATE', module.name, saveVersion); @@ -126,7 +126,7 @@ function showContent(req,res,template,block,next) { */ function saveVersion(event, content, next) { - var ContentVersion = calipso.lib.mongoose.model('ContentVersion'); + var ContentVersion = calipso.db.model('ContentVersion'); // Create version and map fields var version = new ContentVersion(); @@ -163,7 +163,7 @@ function showVersion(req,res,template,block,next) { var id = req.moduleParams.version; var format = req.moduleParams.format || 'html'; - var ContentVersion = calipso.lib.mongoose.model('ContentVersion'); + var ContentVersion = calipso.db.model('ContentVersion'); var vPerm = calipso.permissions.hasPermission("content:versions:view"), @@ -205,7 +205,7 @@ function diffVersion(req,res,template,block,next) { var a = req.moduleParams.a; var b = req.moduleParams.b; - var ContentVersion = calipso.lib.mongoose.model('ContentVersion'); + var ContentVersion = calipso.db.model('ContentVersion'); ContentVersion.findById(a,function(err,versionA) { @@ -263,7 +263,7 @@ function listVersions(req,res,template,block,next) { var id = req.moduleParams.id; // Re-retrieve our object - var ContentVersion = calipso.lib.mongoose.model('ContentVersion'); + var ContentVersion = calipso.db.model('ContentVersion'); var vPerm = calipso.permissions.hasPermission("content:versions:view"), dPerm = calipso.permissions.hasPermission("content:versions:diff"); @@ -307,8 +307,8 @@ function revertVersion(req,res,template,block,next) { var id = req.moduleParams.version; var format = req.moduleParams.format || 'html'; - var Content = calipso.lib.mongoose.model('Content'); - var ContentVersion = calipso.lib.mongoose.model('ContentVersion'); + var Content = calipso.db.model('Content'); + var ContentVersion = calipso.db.model('ContentVersion'); ContentVersion.findById(id,function(err,version) { diff --git a/modules/core/permissions/permissions.js b/modules/core/permissions/permissions.js index c8edb95a5..fa26b7437 100644 --- a/modules/core/permissions/permissions.js +++ b/modules/core/permissions/permissions.js @@ -51,10 +51,10 @@ function init(module, app, next) { permission:{type: String, required: true}, role:{type: String, required: true} }); - calipso.lib.mongoose.model('PermissionRole', PermissionRole); + calipso.db.model('PermissionRole', PermissionRole); loadPermissionRoles(function(err) { - next(); + next(err); }); } @@ -65,7 +65,7 @@ function init(module, app, next) { function loadPermissionRoles(next) { var perm = calipso.permissions, - PermissionRole = calipso.lib.mongoose.model('PermissionRole'); + PermissionRole = calipso.db.model('PermissionRole'); // Clear down first - this may cause strange behaviour to anyone // making a request at just this moment ... @@ -80,19 +80,15 @@ function loadPermissionRoles(next) { perm.addPermissionRole("admin:content:type:view","Contributor"); perm.addPermissionRole("admin:content:type:create","Contributor"); perm.addPermissionRole("admin:content:type:delete","Contributor"); - -perm.addPermissionRole("admin:core:configuration","Contributor"); - -perm.addPermissionRole("content:view","Contributor"); - -perm.addPermissionRole("content:update","Contributor"); - -perm.addPermissionRole("content:create","Contributor"); - - + perm.addPermissionRole("admin:core:configuration","Contributor"); + perm.addPermissionRole("content:view","Contributor"); + perm.addPermissionRole("content:update","Contributor"); + perm.addPermissionRole("content:create","Contributor"); + perm.structureAndSort(); next(); + }); } @@ -103,8 +99,8 @@ perm.addPermissionRole("content:create","Contributor"); function showPermissions(req, res, options, next) { var structuredPermissions = calipso.permissions.structuredPermissions, - Role = calipso.lib.mongoose.model('Role'), - PermissionRole = calipso.lib.mongoose.model('PermissionRole'); + Role = calipso.db.model('Role'), + PermissionRole = calipso.db.model('PermissionRole'); Role.find({}).sort('name',1).find(function (err, roles) { var output = renderPermissionTable(structuredPermissions, roles); diff --git a/modules/core/scheduler/scheduler.js b/modules/core/scheduler/scheduler.js index 25ea5b010..0b197764b 100644 --- a/modules/core/scheduler/scheduler.js +++ b/modules/core/scheduler/scheduler.js @@ -60,7 +60,7 @@ function init(module,app,next) { method:{type: String, "default":'', required: true}, args:{type: String, "default":'', required: false} }); - calipso.lib.mongoose.model('ScheduledJob', ScheduledJob); + calipso.db.model('ScheduledJob', ScheduledJob); // Load the exposed job functions into a job function array // This scans all the other modules @@ -87,7 +87,7 @@ function init(module,app,next) { */ function loadJobs(next) { - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); // Check to see if we already have any jobs. // Create a holder for our jobs - DOES THIS STOP EVERYTHING ELSE??! @@ -194,7 +194,7 @@ function schedulerAdmin(req,res,template,block,next) { res.menu.adminToolbar.addMenuItem(req, {name:'New Job',path:'new',url:'/scheduler/new',description:'Create new job ...',security:[]}); - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); // Render json to blocks var item = {id:"NA",type:'content',meta:{jobs:calipso.jobs}}; @@ -257,7 +257,7 @@ function createJob(req,res,template,block,next) { if(form) { - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); var job = new ScheduledJob(processForm(form.job)); @@ -342,7 +342,7 @@ function processForm(formObject) { */ function editJobForm(req,res,template,block,next) { - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); var jobName = req.moduleParams.jobName; var item; @@ -397,7 +397,7 @@ function updateJob(req,res,template,block,next) { if(form) { - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); var jobName = req.moduleParams.jobName; ScheduledJob.findOne({name:jobName}, function(err, job) { @@ -474,7 +474,7 @@ function updateJob(req,res,template,block,next) { */ function showJob(req,res,template,block,next,err) { - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); var jobName = req.moduleParams.jobName; var item; @@ -506,7 +506,7 @@ function showJob(req,res,template,block,next,err) { */ function deleteJob(req,res,template,block,next,err) { - var ScheduledJob = calipso.lib.mongoose.model('ScheduledJob'); + var ScheduledJob = calipso.db.model('ScheduledJob'); var jobName = req.moduleParams.jobName; ScheduledJob.remove({name:jobName}, function(err) { diff --git a/modules/core/tagcloud/tagcloud.js b/modules/core/tagcloud/tagcloud.js index d965ae623..b0a4ed614 100644 --- a/modules/core/tagcloud/tagcloud.js +++ b/modules/core/tagcloud/tagcloud.js @@ -39,7 +39,7 @@ function init(module,app,next) { "value":{type: Number} }); - calipso.lib.mongoose.model('Tag', Tag); + calipso.db.model('Tag', Tag); // Register for events calipso.e.post('CONTENT_CREATE',module.name,mapReduceTagCloud); @@ -92,7 +92,7 @@ function mapReduceTagCloud(event,options,next) { out: 'tags' // what collection are we outputting to? mongo 1.7.4 + is different see http://www.mongodb.org/display/DOCS/MapReduce#MapReduce-Outputoptions }; - mongoose.connection.db.executeDbCommand(command, function(err, dbres) + calipso.db.db.executeDbCommand(command, function(err, dbres) { // Reset @@ -112,7 +112,7 @@ function mapReduceTagCloud(event,options,next) { */ function tagCloud(req,res,template,block,next) { - var Tag = calipso.lib.mongoose.model('Tag'); + var Tag = calipso.db.model('Tag'); Tag.find({}) .find(function (err, tags) { diff --git a/modules/core/taxonomy/taxonomy.js b/modules/core/taxonomy/taxonomy.js index 3a927ecb2..1351f09d0 100644 --- a/modules/core/taxonomy/taxonomy.js +++ b/modules/core/taxonomy/taxonomy.js @@ -47,7 +47,7 @@ function init(module,app,next) { "value":{type: Number} }); - calipso.lib.mongoose.model('TaxonomyMenu', TaxonomyMenu); + calipso.db.model('TaxonomyMenu', TaxonomyMenu); // Register for events calipso.e.post('CONTENT_CREATE',module.name,mapReduceTaxonomy); @@ -111,7 +111,7 @@ function mapReduceTaxonomy(event,options,next) { out: 'taxonomymenus' // what collection are we outputting to? mongo 1.7.4 + is different see http://www.mongodb.org/display/DOCS/MapReduce#MapReduce-Outputoptions }; - mongoose.connection.db.executeDbCommand(command, function(err, dbres) + calipso.db.db.executeDbCommand(command, function(err, dbres) { // Reset calipso.mr.taxonomy = false; @@ -133,7 +133,7 @@ function taxonomy(req,res,template,block,next) { return; // Generate the menu from the taxonomy - var TaxonomyMenu = calipso.lib.mongoose.model('TaxonomyMenu'); + var TaxonomyMenu = calipso.db.model('TaxonomyMenu'); TaxonomyMenu.find({},function (err, tax) { // Render the item into the response diff --git a/modules/core/user/user.js b/modules/core/user/user.js index 85949e9a0..41cfd2506 100644 --- a/modules/core/user/user.js +++ b/modules/core/user/user.js @@ -75,7 +75,7 @@ function init(module, app, next) { isAdmin:{type: Boolean, required: true, "default": false}, isDefault:{type: Boolean, required: true, "default": false} }); - calipso.lib.mongoose.model('Role', Role); + calipso.db.model('Role', Role); var User = new calipso.lib.mongoose.Schema({ // Single default property @@ -92,7 +92,7 @@ function init(module, app, next) { locked:{type: Boolean, "default":false} }); - calipso.lib.mongoose.model('User', User); + calipso.db.model('User', User); next(); // Load roles into calipso data @@ -133,7 +133,7 @@ function setCookie(req, res, template, block, next) { */ function storeRoles() { - var Role = calipso.lib.mongoose.model('Role'); + var Role = calipso.db.model('Role'); delete calipso.data.roleArray; delete calipso.data.roles; @@ -171,7 +171,7 @@ function userDisplay(req, username, next) { var isAdmin = (req.session.user && req.session.user.isAdmin); var isUser = (req.session.user); - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var responseData = {name:'',email:''}; User.findOne({username:username}, function(err, u) { @@ -320,7 +320,7 @@ function updateUserForm(req, res, template, block, next) { res.layout = 'admin'; var isAdmin = (req.session.user && req.session.user.isAdmin); - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var username = req.moduleParams.username; var roleSection = 3; // Update if changing sections @@ -428,7 +428,7 @@ function updateUserForm(req, res, template, block, next) { */ function lockUser(req, res, template, block, next) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var username = req.moduleParams.username; User.findOne({username:username}, function(err, u) { @@ -461,7 +461,7 @@ function lockUser(req, res, template, block, next) { */ function unlockUser(req, res, template, block, next) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var username = req.moduleParams.username; User.findOne({username:username}, function(err, u) { @@ -497,7 +497,7 @@ function updateUserProfile(req, res, template, block, next) { if(form) { var username = req.moduleParams.username; - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); // Quickly check that the user is an admin or it is their account if(req.session.user && (req.session.user.isAdmin || req.session.user.username === username)) { @@ -626,7 +626,7 @@ function loginUser(req, res, template, block, next) { calipso.form.process(req, function(form) { if(form) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var username = form.user.username; var found = false; @@ -695,7 +695,7 @@ function logoutUser(req, res, template, block, next) { if(req.session && req.session.user) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); User.findOne({username:req.session.user.username}, function(err, u) { @@ -729,7 +729,7 @@ function registerUser(req, res, template, block, next) { if(form) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); // Get the password values and remove from form // This ensures they are never stored @@ -826,7 +826,7 @@ function myProfile(req, res, template, block, next) { */ function userProfile(req, res, template, block, next) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var username = req.moduleParams.username; User.findOne({username:username}, function(err, u) { @@ -863,7 +863,7 @@ function userProfile(req, res, template, block, next) { */ function deleteUser(req, res, template, block, next) { - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); var username = req.moduleParams.username; User.findOne({username:username}, function(err, u) { @@ -899,7 +899,7 @@ function userLink(req,user) { function listUsers(req,res,template,block,next) { // Re-retrieve our object - var User = calipso.lib.mongoose.model('User'); + var User = calipso.db.model('User'); res.menu.adminToolbar.addMenuItem(req, {name:'Register New User',path:'new',url:'/user/register',description:'Register new user ...',security:[]}); @@ -972,8 +972,8 @@ function listUsers(req,res,template,block,next) { function install(next) { // Create the default content types - var Role = calipso.lib.mongoose.model('Role'); - var User = calipso.lib.mongoose.model('User'); + var Role = calipso.db.model('Role'); + var User = calipso.db.model('User'); calipso.lib.step( diff --git a/package.json b/package.json index aac3d827c..4a1bf5207 100644 --- a/package.json +++ b/package.json @@ -35,13 +35,11 @@ "request":"2.9.x", "pool":"0.4.x", "mime":"1.2.x", - "cluster": "0.7.x", "step": "0.0.x", "optimist":"0.3.x", "colors":"0.6.x", "bcrypt": "0.5.x", "semver":"1.0.x", - "hook.io":"0.8.x", "zipfile":"0.3.x", "rimraf":"2.0.x", "nconf":"0.5.x", diff --git a/themes/core/cleanslate/templates/admin.html b/themes/core/cleanslate/templates/admin.html index 20982a6ac..e611d338a 100644 --- a/themes/core/cleanslate/templates/admin.html +++ b/themes/core/cleanslate/templates/admin.html @@ -39,4 +39,4 @@ <%- scripts %> - + \ No newline at end of file