diff --git a/factories/app/index.js b/factories/app/index.js index 288ef628..d8a6cb16 100644 --- a/factories/app/index.js +++ b/factories/app/index.js @@ -8,30 +8,28 @@ var ModuleFactory = require('../../lib/factory'); /** * Creates a new instance of ApplicationFactory. */ -function ApplicationFactory() { +function ApplicationFactory(root) { if (!(this instanceof ApplicationFactory)) { - return new ApplicationFactory(); + return new ApplicationFactory(root, __dirname); } - ModuleFactory.call(this); + ModuleFactory.call(this, root, __dirname); } util.inherits(ApplicationFactory, ModuleFactory); -/** - * See ModuleFactory.render. - */ -ApplicationFactory.prototype.render = render; -function render(root, options, callback) { - var self = this; - - if (!options.name) { - options.name = 'LoopBack'; - } - - self.renderer.renderAll(path.join(__dirname, 'template'), root, options, callback); - - return self; -} +ApplicationFactory.defaults = { + "editor": "/tools/app-edit/editor.html", + "port": 3000, + "hostname": '0.0.0.0', + "middleware": [ + "compress", + "favicon", + "logger", + "bodyParser", + "methodOverride", + "rest" + ] +}; /** * See ModuleFactory.dependencies. diff --git a/factories/app/template/config.json b/factories/app/template/config.json deleted file mode 100644 index 7dae9d87..00000000 --- a/factories/app/template/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - {?port} - "port": {port}, - {:else} - "port": 3000, - {/port} - {?hostname} - "hostname": {hostname}, - {:else} - "hostname": "0.0.0.0", - {/hostname} - "middleware": [ - "compress", - "favicon", - "logger", - "bodyParser", - "methodOverride", - "rest" - ] -} diff --git a/factories/app/template/module.json b/factories/app/template/module.json deleted file mode 100644 index b57a1b40..00000000 --- a/factories/app/template/module.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "editor": "/tools/app-edit/editor.html", - "source": {.|js|s} -} diff --git a/factories/data-source-memory/index.js b/factories/data-source-memory/index.js deleted file mode 100644 index 77a36b16..00000000 --- a/factories/data-source-memory/index.js +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * TODO: Description. - */ -var path = require('path'); -var util = require('util'); -var ModuleFactory = require('../../lib/factory'); - -/** - * Creates a new instance of DataSourceFactory. - */ -function DataSourceFactory() { - if (!(this instanceof DataSourceFactory)) { - return new DataSourceFactory(); - } - - ModuleFactory.call(this); -} -util.inherits(DataSourceFactory, ModuleFactory); - -/** - * See ModuleFactory.render. - */ -DataSourceFactory.prototype.render = render; -function render(root, options, callback) { - var self = this; - - self.renderer.renderAll(path.join(__dirname, 'template'), root, options, callback); - - return self; -} - -/** - * See ModuleFactory.dependencies. - */ -DataSourceFactory.prototype.dependencies = dependencies; -function dependencies() { - return {}; -} - -/*! - * Export `DataSourceFactory`. - */ -module.exports = DataSourceFactory; diff --git a/factories/data-source-memory/template/index.js b/factories/data-source-memory/template/index.js deleted file mode 100644 index ba2b3c81..00000000 --- a/factories/data-source-memory/template/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * An in-memory DataSource for development. - */ -var loopback = require('loopback'); - -module.exports = loopback.createDataSource({ - connector: loopback.Memory -}); diff --git a/factories/data-source-memory/template/module.json b/factories/data-source-memory/template/module.json deleted file mode 100644 index 63529811..00000000 --- a/factories/data-source-memory/template/module.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "editor": "/tools/data-source-memory-edit/editor.html", - "source": {.|js|s} -} diff --git a/factories/data-source/index.js b/factories/data-source/index.js new file mode 100644 index 00000000..a8b92a7e --- /dev/null +++ b/factories/data-source/index.js @@ -0,0 +1,28 @@ +/*! + * TODO: Description. + */ +var path = require('path'); +var util = require('util'); +var ModuleFactory = require('../../lib/factory'); + +/** + * Creates a new instance of DataSourceFactory. + */ +function DataSourceFactory(root) { + if (!(this instanceof DataSourceFactory)) { + return new DataSourceFactory(root, __dirname); + } + + ModuleFactory.call(this, root, __dirname); +} +util.inherits(DataSourceFactory, ModuleFactory); + +DataSourceFactory.defaults = { + "editor": "/tools/data-source-memory-edit/editor.html", + "connector": "memory" +}; + +/*! + * Export `DataSourceFactory`. + */ +module.exports = DataSourceFactory; diff --git a/factories/data-source/template/index.js b/factories/data-source/template/index.js new file mode 100644 index 00000000..6dea5446 --- /dev/null +++ b/factories/data-source/template/index.js @@ -0,0 +1,7 @@ +/*! + * An in-memory DataSource for development. + */ +var loopback = require('loopback'); +var options = require('./module.json'); + +module.exports = loopback.createDataSource(options); diff --git a/factories/docs/index.js b/factories/docs/index.js index f43f1230..b6f27a4e 100644 --- a/factories/docs/index.js +++ b/factories/docs/index.js @@ -10,12 +10,12 @@ var ModuleFactory = require('../../lib/factory'); /** * Creates a new instance of DocsExplorerFactory. */ -function DocsExplorerFactory() { +function DocsExplorerFactory(root) { if (!(this instanceof DocsExplorerFactory)) { - return new DocsExplorerFactory(); + return new DocsExplorerFactory(root, __dirname); } - ModuleFactory.call(this); + ModuleFactory.call(this, root, __dirname); } util.inherits(DocsExplorerFactory, ModuleFactory); @@ -32,7 +32,6 @@ function render(root, options, callback) { return; } - console.log(root); cpr(path.join(__dirname, 'explorer'), path.join(root, 'explorer'), { overwrite: true, confirm: true @@ -49,14 +48,6 @@ function render(root, options, callback) { return self; } -/** - * See ModuleFactory.dependencies. - */ -DocsExplorerFactory.prototype.dependencies = dependencies; -function dependencies() { - return {}; -} - /*! * Export `DocsExplorerFactory`. */ diff --git a/factories/docs/template/config.json b/factories/docs/template/config.json deleted file mode 100644 index cf169b62..00000000 --- a/factories/docs/template/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "url": "{url}", - "applications": [{#applications}"{.}"{@sep}, {/sep}{/applications}] -} diff --git a/factories/docs/template/index.js b/factories/docs/template/index.js index e03fffc3..7893e951 100644 --- a/factories/docs/template/index.js +++ b/factories/docs/template/index.js @@ -3,25 +3,22 @@ */ var path = require('path'); var loopback = require('loopback'); -var config = require('./config'); +var options = require('./module.json'); var STATIC_ROOT = path.join(__dirname, 'explorer'); -var applications = config.applications || []; -var explorerUrl = config.url || '/explorer'; +var explorerUrl = options.url || '/explorer'; process.nextTick(function () { - applications.forEach(function (name) { - var app = require('../' + name); + var app = require('../app'); - app.docs({ basePath: '/' }); - app.get(explorerUrl, function (req, res, next) { - if (!/\/$/.test(req.url)) { - res.redirect(req.url + '/'); - } else { - next(); - } - }); - app.use(explorerUrl, loopback.static(STATIC_ROOT)); + app.docs({ basePath: '/' }); + app.get(explorerUrl, function (req, res, next) { + if (!/\/$/.test(req.url)) { + res.redirect(req.url + '/'); + } else { + next(); + } }); + app.use(explorerUrl, loopback.static(STATIC_ROOT)); }); module.exports = {}; diff --git a/factories/docs/template/module.json b/factories/docs/template/module.json deleted file mode 100644 index f4497980..00000000 --- a/factories/docs/template/module.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "source": {.|js|s} -} diff --git a/factories/email/index.js b/factories/email/index.js new file mode 100644 index 00000000..4a5e1720 --- /dev/null +++ b/factories/email/index.js @@ -0,0 +1,29 @@ +/*! + * TODO: Description. + */ +var path = require('path'); +var util = require('util'); +var ModuleFactory = require('../../lib/factory'); + +/** + * Creates a new instance of EmailFactory. + */ +function EmailFactory(root) { + if (!(this instanceof EmailFactory)) { + return new EmailFactory(root, __dirname); + } + + ModuleFactory.call(this, root, __dirname); +} +util.inherits(EmailFactory, ModuleFactory); + +EmailFactory.defaults = { + name: 'email', + properties: {}, + config: {'data-source': 'mail'} +}; + +/*! + * Export `EmailFactory`. + */ +module.exports = EmailFactory; diff --git a/factories/email/template/index.js b/factories/email/template/index.js new file mode 100644 index 00000000..57106a98 --- /dev/null +++ b/factories/email/template/index.js @@ -0,0 +1,13 @@ +/*! + * A CRUD-capable model. + */ +var loopback = require('loopback'); +var options = require('./module.json'); +var {className} = loopback.Email.extend('{dashedName}', + options.properties, options.config); + +if (options.config['data-source']) { + {className}.attachTo(require('../' + config['data-source'])); +} + +module.exports = {className}; diff --git a/factories/model/index.js b/factories/model/index.js index 65420ccd..9ea63383 100644 --- a/factories/model/index.js +++ b/factories/model/index.js @@ -8,35 +8,15 @@ var ModuleFactory = require('../../lib/factory'); /** * Creates a new instance of ModelFactory. */ -function ModelFactory() { +function ModelFactory(root) { if (!(this instanceof ModelFactory)) { - return new ModelFactory(); + return new ModelFactory(root, __dirname); } - ModuleFactory.call(this); + ModuleFactory.call(this, root, __dirname); } util.inherits(ModelFactory, ModuleFactory); -/** - * See ModuleFactory.render. - */ -ModelFactory.prototype.render = render; -function render(root, options, callback) { - var self = this; - - self.renderer.renderAll(path.join(__dirname, 'template'), root, options, callback); - - return self; -} - -/** - * See ModuleFactory.dependencies. - */ -ModelFactory.prototype.dependencies = dependencies; -function dependencies() { - return {}; -} - /*! * Export `ModelFactory`. */ diff --git a/factories/model/template/config.json b/factories/model/template/config.json deleted file mode 100644 index d8bf33db..00000000 --- a/factories/model/template/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - {?data-source} - "data-source": "{data-source}", - {/data-source} - {?public} - "public": true - {:else} - "public": false - {/public} -} diff --git a/factories/model/template/index.js b/factories/model/template/index.js index b68e9d64..d8ffda3d 100644 --- a/factories/model/template/index.js +++ b/factories/model/template/index.js @@ -2,12 +2,12 @@ * A CRUD-capable model. */ var loopback = require('loopback'); -var properties = require('./properties'); -var config = require('./config'); -var {name} = loopback.Model.extend('{name}', properties, config); +var options = require('./module.json'); +var {className} = loopback.Model.extend('{dashedName}', + options.properties, options.config); if (config['data-source']) { - {name}.attachTo(require('../' + config['data-source'])); + {className}.attachTo(require('../' + config['data-source'])); } -module.exports = {name}; +module.exports = {className}; diff --git a/factories/model/template/module.json b/factories/model/template/module.json deleted file mode 100644 index a977ce85..00000000 --- a/factories/model/template/module.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "editor": "/tools/model-edit/editor.html", - "source": {.|js|s} -} diff --git a/factories/model/template/properties.json b/factories/model/template/properties.json deleted file mode 100644 index b538d082..00000000 --- a/factories/model/template/properties.json +++ /dev/null @@ -1,5 +0,0 @@ -{?properties} -{.properties|s|js} -{:else} -{} -{/properties} diff --git a/factories/project/index.js b/factories/project/index.js new file mode 100644 index 00000000..732c3b48 --- /dev/null +++ b/factories/project/index.js @@ -0,0 +1,23 @@ +/*! + + */ +var path = require('path'); +var util = require('util'); +var ModuleFactory = require('../../lib/factory'); + +/** + * Creates a new instance of ProjectFactory. + */ +function ProjectFactory(root) { + if (!(this instanceof ProjectFactory)) { + return new ProjectFactory(root); + } + + ModuleFactory.call(this, root, __dirname); +} +util.inherits(ProjectFactory, ModuleFactory); + +/*! + * Export `ProjectFactory`. + */ +module.exports = ProjectFactory; diff --git a/data/project-template/app.js b/factories/project/template/app.js similarity index 78% rename from data/project-template/app.js rename to factories/project/template/app.js index 530653ee..07340741 100644 --- a/data/project-template/app.js +++ b/factories/project/template/app.js @@ -1,20 +1,21 @@ -/*! - * App Dependencies. +/** + * Start profiling the app. See: http://docs.strongloop.com/strongops/ */ - require('strong-agent').profile(); +/*! + * App Dependencies. + */ var control = require('strong-cluster-control'); -var options = control.loadOptions(); +var clusterOptions = control.loadOptions(); +var fs = require('fs'); +var path = require('path'); // If configured as a cluster master, just start controller -if(options.clustered && options.isMaster) { - return control.start(options); +if(clusterOptions.clustered && clusterOptions.isMaster) { + return control.start(clusterOptions); } -var fs = require('fs'); -var path = require('path'); - /** * Synchronously loads all modules within the project. * diff --git a/data/project-template/package.json b/factories/project/template/package.json similarity index 100% rename from data/project-template/package.json rename to factories/project/template/package.json diff --git a/factories/session/index.js b/factories/session/index.js new file mode 100644 index 00000000..77e6cae7 --- /dev/null +++ b/factories/session/index.js @@ -0,0 +1,29 @@ +/*! + * TODO: Description. + */ +var path = require('path'); +var util = require('util'); +var ModuleFactory = require('../../lib/factory'); + +/** + * Creates a new instance of SessionFactory. + */ +function SessionFactory(root) { + if (!(this instanceof SessionFactory)) { + return new SessionFactory(root, __dirname); + } + + ModuleFactory.call(this, root, __dirname); +} +util.inherits(SessionFactory, ModuleFactory); + +SessionFactory.defaults = { + name: 'session', + properties: {}, + config: {'data-source': 'db'} +}; + +/*! + * Export `SessionFactory`. + */ +module.exports = SessionFactory; diff --git a/factories/session/template/index.js b/factories/session/template/index.js new file mode 100644 index 00000000..a98112cf --- /dev/null +++ b/factories/session/template/index.js @@ -0,0 +1,13 @@ +/*! + * A CRUD-capable model. + */ +var loopback = require('loopback'); +var options = require('./module.json'); +var {className} = loopback.Session.extend('{dashedName}', + options.properties, options.config); + +if (options.config['data-source']) { + {className}.attachTo(require('../' + config['data-source'])); +} + +module.exports = {className}; diff --git a/factories/user/index.js b/factories/user/index.js new file mode 100644 index 00000000..531d1c3d --- /dev/null +++ b/factories/user/index.js @@ -0,0 +1,29 @@ +/*! + * TODO: Description. + */ +var path = require('path'); +var util = require('util'); +var ModuleFactory = require('../../lib/factory'); + +/** + * Creates a new instance of UserFactory. + */ +function UserFactory(root) { + if (!(this instanceof UserFactory)) { + return new UserFactory(root, __dirname); + } + + ModuleFactory.call(this, root, __dirname); +} +util.inherits(UserFactory, ModuleFactory); + +UserFactory.defaults = { + name: 'user', + properties: {}, + config: {'data-source': 'db'} +}; + +/*! + * Export `UserFactory`. + */ +module.exports = UserFactory; diff --git a/factories/user/template/index.js b/factories/user/template/index.js new file mode 100644 index 00000000..5306f533 --- /dev/null +++ b/factories/user/template/index.js @@ -0,0 +1,18 @@ +/*! + * A CRUD-capable model. + */ +var loopback = require('loopback'); +var options = require('./module.json'); +var emailModel = options.config.emailModel || 'email'; +var sessionModel = options.config.sessionModel || 'session'; +var {className} = loopback.Email.extend('{dashedName}', + options.properties, options.config); + +if (options.config['data-source']) { + {className}.attachTo(require('../' + config['data-source'])); +} + +{className}.email = require('../' + emailModel); +{className}.session = require('../' + sessionModel); + +module.exports = {className}; diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 00000000..fb78ef24 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,20 @@ +var CONNECTORS = { + memory: 'memory' +}; + +var MODULE_TYPES = { + dataSource: 'data-source', + model: 'model', + userModel: 'user', + emailModel: 'email', + sessionModel: 'session', + notificationModel: 'notification', + deviceModel: 'device', + app: 'app' +}; + +module.exports = { + CONNECTORS: CONNECTORS, + MODULE_TYPES: MODULE_TYPES +}; + diff --git a/lib/factory.js b/lib/factory.js index 8a9a6e3c..bb3dcaf5 100644 --- a/lib/factory.js +++ b/lib/factory.js @@ -1,15 +1,78 @@ /*! - * TODO: Description. + * Dependencies. */ var assert = require('assert'); var Generator = require('strong-generator'); +var fs = require('fs'); +var async = require('async'); +var path = require('path'); +var _ = require('underscore'); +var stringUtils = require('underscore.string'); /** * Creates a new instance of ModuleFactory. */ -function ModuleFactory() { +function ModuleFactory(root, source) { if (!(this instanceof ModuleFactory)) { - return new ModuleFactory(); + return new ModuleFactory(root, source); + } + + assert(typeof root === 'string', this.constructor.name + + ' must call ModuleFactory with a root'); + assert(source, this.constructor.name + + ' must call ModuleFactory with __dirname'); + this.root = root; + this.source = source; + this.template = path.join(source, 'template'); +} + +ModuleFactory.prototype.create = function (options, callback) { + var Ctor = this.constructor; + var root = this.root; + var factory = this; + var defaults = Ctor.defaults || {}; + + factory.exists(function (exists) { + if(exists) { + callback(new Error(root + ' already exists')); + } else { + options = _.defaults(options, defaults); + async.waterfall([ + function (callback) { + factory.render(options, callback); + }, + function (callback) { + factory.setOptions(options, callback); + } + ], callback); + } + }); +} + +ModuleFactory.prototype.exists = function (callback) { + fs.exists(this.root, callback); +} + +/** + * Synchronously loads an installed ModuleFactory at `name`. + * + * @param {String} name The factory to load. + * @returns {ModuleFactory} A new ModuleFactory instance, or null if the named + * factory is not installed. + */ +ModuleFactory.factory = factory; +function factory(name, root) { + if (!name) { + return null; + } + + try { + return require(path.join(__dirname, '..', 'factories', name))(root); + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + throw e; + } + return null; } } @@ -29,8 +92,23 @@ ModuleFactory.prototype.renderer = new Generator(); * @returns {ModuleFactory} The ModuleFactory instance, for cascading. */ ModuleFactory.prototype.render = render; -function render(root, options, callback) { - assert(false, 'Invalid ModuleFactory class without render implementation: ' + self.constructor.name); +function render(options, callback) { + this.renderer.renderAll(this.template, this.root, this.toJavaScript(options), + function (err) { + callback(err); + }); +} + +ModuleFactory.prototype.toJavaScript = toJavaScript; +function toJavaScript(options) { + var cloned = _.clone(options); + + if(typeof cloned.name === 'string') { + cloned.className = stringUtils.classify(cloned.name); + cloned.dashedName = stringUtils.dasherize(cloned.name); + } + + return cloned; } /** @@ -45,6 +123,55 @@ function dependencies() { return {}; } +ModuleFactory.prototype.setOptions = setOptions; +function setOptions(options, callback) { + var factory = this; + var merged; + + this.getOptions(function (err, current) { + if(err) { + callback(err); + } else { + try { + merged = JSON.stringify(_.extend(current, options)); + } catch(e) { + return callback(e); + } + + fs.writeFile(factory.getOptionsFilePath(), merged, 'utf8', callback); + } + }); +} + +ModuleFactory.prototype.getOptions = getOptions; +function getOptions(callback) { + var options; + var self = this; + fs.readFile(this.getOptionsFilePath(), 'utf8', function (err, str) { + if(err) { + if(err.code === 'ENOENT') { + return callback(null, {}); + } + callback(err); + } else if(str.replace(' ', '')) { + try { + options = JSON.parse(str); + } catch(e) { + return callback(e); + } + + callback(null, options); + } else { + callback(null, {}); + } + }); +} + +ModuleFactory.prototype.getOptionsFilePath = getOptionsFilePath; +function getOptionsFilePath() { + return path.join(this.root, 'module.json'); +} + /*! * Export `ModuleFactory`. */ diff --git a/lib/project.js b/lib/project.js index 1cb4391f..bf4992d9 100644 --- a/lib/project.js +++ b/lib/project.js @@ -8,7 +8,8 @@ var async = require('async'); var debug = require('debug')('loopback:Project'); var rimraf = require('rimraf'); var generator = require('strong-generator').createGenerator(); -var TEMPLATE_PATH = path.join(__dirname, '..', 'data', 'project-template'); +var ModuleFactory = require('./factory'); +var ProjectFactory = require('../factories/project'); /** * Creates a new instance of Project with the provided `options`. @@ -87,24 +88,79 @@ function isModule(root, callback) { Project.create = create; function create(root, options, callback) { var cls = this; + var template = options.template || 'mobile'; + var templateData; + var project = new Project({ + root: root, + name: options.name, + description: options.description + }); + + try { + templateData = require('../templates/' + template); + } catch(e) { + if(e.code === 'MODULE_NOT_FOUND') { + e = new Error('template ' + template + ' does not exist'); + } + return callback(e); + } debug('Rendering a new Project in %s...', root); - - generator.renderAll(TEMPLATE_PATH, root, options, function (err) { - callback(err, err ? null : new Project({ - root: root, - name: options.name, - description: options.description - })); + + async.waterfall([ + function (callback) { + project.renderBoilerplate(callback); + }, + function (callback) { + project.renderTemplate(templateData, callback); + } + ], function (err) { + if(err) { + return callback(err); + } else { + callback(null, project); + } }); return cls; } +Project.prototype.renderBoilerplate = renderBoilerplate; +function renderBoilerplate(callback) { + var projectFactory = new ProjectFactory(this.root); + + projectFactory.render(this, function (err) { + callback(err); + }); +} + +Project.prototype.renderTemplate = renderTemplate; +function renderTemplate(templateData, callback) { + var project = this; + var modules = templateData.modules; + var ops = []; + + assert(typeof templateData === 'object'); + assert(typeof templateData.modules === 'object'); + + Object.keys(modules).forEach(function (moduleName) { + ops.push(function (callback) { + var moduleInfo = modules[moduleName]; + var dir = moduleName; + var options = moduleInfo; + + project.addModule(moduleInfo.type, dir, options, callback); + }); + }); + + async.parallel(ops, callback); +} + /** * Loads metadata from the existing Project. * - * @param {Function} callback A Node-style callback to be called with either an Error or the Project instance. + * @param {Function} callback A Node-style callback to be called with either an + * Error or the Project instance. * @returns {Project} The Project instance, for cascading. */ Project.prototype.load = load; @@ -177,56 +233,6 @@ function getModules(callback) { return self; } -/** - * Retrieves the config for a single Module instance. - */ -Project.prototype.getModuleConfig = getModuleConfig; -function getModuleConfig(dir, callback) { - var self = this; - - fs.readFile(path.join(self.root, 'modules', dir, 'config.json'), 'utf8', function (err, json) { - if (err) { - callback(err); - return; - } - - try { - callback(null, JSON.parse(json)); - } catch (e) { - callback(e); - return; - } - }); - - return self; -} - - -/** - * Retrieves the config for a single Module instance. - */ -Project.prototype.setModuleConfig = setModuleConfig; -function setModuleConfig(dir, config, callback) { - var self = this; - var data; - - try { - data = JSON.stringify(config, null, 2); - } catch(e) { - return callback(e); - } - - fs.writeFile(path.join(self.root, 'modules', dir, 'config.json'), data, function (err) { - if (err) { - callback(err); - return; - } - callback(); - }); - - return self; -} - /** * Retrieves the metadata for a single Module instance. */ @@ -255,22 +261,37 @@ function getModule(dir, callback) { /** * Adds a new instance of `factory` as a Module at `dir`. * - * @param {ModuleFactory} factory The factory to use for rendering the new Module instance. + * @param {String} type The type of factory to use for rendering the new Module instance. * @param {String} dir The directory within `modules` to place the new Module in. * @param {Object} options The options to use while rendering the Module instance. * @param {Function} callback An error-only callback. * @returns {Project} The Project instance, for cascading. */ Project.prototype.addModule = addModule; -function addModule(factory, dir, options, callback) { +function addModule(type, dir, options, callback) { var self = this; + var factory = ModuleFactory.factory( + type, path.join(self.root, 'modules', dir) + ); + + assert(factory, 'Factory of type ' + type + ' was not found'); async.waterfall([ - function (cbRender) { - factory.render(path.join(self.root, 'modules', dir), options, cbRender); + function (cbCreate) { + factory.create(options, function (err) { + if(err) { + cbCreate(err); + } else { + cbCreate(); + } + }); }, fs.readFile.bind(fs, path.join(self.root, 'package.json'), 'utf8'), function (json, cbMerge) { + // TODO(ritch) - waterfall needs reworking so an empty JSON file isn't written + if(!json.replace(' ', '')) { + return cbMerge(); + } var obj = JSON.parse(json); var deps = factory.dependencies(); diff --git a/lib/server.js b/lib/server.js index eaf3d075..4c62a6cc 100644 --- a/lib/server.js +++ b/lib/server.js @@ -215,8 +215,8 @@ function getModuleForProject(req, res, next) { Server.prototype.addModuleToProject = addModuleToProject; function addModuleToProject(req, res, next) { var self = this; - var factory = self.workspace.factory(req.body.type); var dir = req.params.name; + var factory = self.workspace.factory(req.body.type, dir); var subdir = req.params.subname; if (!factory) { diff --git a/lib/workspace.js b/lib/workspace.js index a0871609..9f361488 100644 --- a/lib/workspace.js +++ b/lib/workspace.js @@ -106,21 +106,7 @@ function get(dir, callback) { * @param {String} name The factory to load. * @returns {ModuleFactory} A new ModuleFactory instance, or null if the named factory is not installed. */ -Workspace.prototype.factory = factory; -function factory(name) { - if (!name) { - return null; - } - - try { - return require(path.join(__dirname, '..', 'factories', name))(); - } catch (e) { - if (e.message.indexOf('Cannot find module') === -1) { - throw e; - } - return null; - } -} +Workspace.prototype.factory = require('./factory').factory; /*! * Export `Workspace`. diff --git a/package.json b/package.json index c9dbb025..115300b0 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "rimraf": "~2.2.0", "async": "~0.2.9", "strong-generator": "git+ssh://git@github.com:strongloop/strong-generator.git", - "cpr": "~0.1.1" + "cpr": "~0.1.1", + "underscore": "~1.5.2", + "underscore.string": "~2.3.3" }, "devDependencies": { "mocha": "~1.11.0", diff --git a/templates/empty.js b/templates/empty.js new file mode 100644 index 00000000..0ed100bc --- /dev/null +++ b/templates/empty.js @@ -0,0 +1,16 @@ +var constants = require('../lib/constants') + , CONNECTORS = constants.CONNECTORS + , TYPES = constants.MODULE_TYPES; + +module.exports = { + description: 'An empty backend', + modules: { + db: { + type: TYPES.dataSource, + connector: CONNECTORS.memory + }, + app: { + type: TYPES.app + } + } +}; \ No newline at end of file diff --git a/templates/mobile.js b/templates/mobile.js new file mode 100644 index 00000000..663a4334 --- /dev/null +++ b/templates/mobile.js @@ -0,0 +1,73 @@ +var constants = require('../lib/constants') + , CONNECTORS = constants.CONNECTORS + , TYPES = constants.MODULE_TYPES; + +var template = { + description: 'A mobile backend', + modules: { + app: { + type: TYPES.app, + // models => listModels() + }, + // data sources + db: { + type: TYPES.dataSource, + connector: CONNECTORS.memory + }, + mail: { + type: TYPES.dataSource, + connector: CONNECTORS.mail + }, + // 'file-storage': { + // type: TYPES.dataSource, + // connector: CONNECTORS.file + // }, + // push: { + // type: TYPES.dataSource, + // connector: CONNECTORS.push + // }, + // models + user: { + type: TYPES.userModel, + }, + session: { + type: TYPES.sessionModel, + }, + email: { + type: TYPES.emailModel, + }, + // notification: { + // type: TYPES.notificationModel, + // 'data-source': 'push' + // }, + // file: { + // type: TYPES.fileModel, + // 'data-source': 'file' + // }, + // device: { + // type: TYPES.deviceModel, + // 'data-source': 'db' + // } + } +}; + +template.modules.app.models = listModels(template); + +module.exports = template; + +function listModels() { + var modelNames = []; + var module; + var isModel; + + Object.keys(template.modules).forEach(function (moduleName) { + module = template.modules[moduleName]; + isModel = !!module['data-source']; + + if(isModel) { + modelNames.push(moduleName); + } + }); + + return modelNames; +} \ No newline at end of file diff --git a/test/project.js b/test/project.js index 6a44e38f..70f3fc3d 100644 --- a/test/project.js +++ b/test/project.js @@ -117,6 +117,32 @@ describe('Project', function () { }); }); }); + + describe('renderTemplate', function () { + before(function () { + var self = this; + + self.tempdir = tempPath(); + fs.mkdirSync(self.tempdir); + + this.project = new Project({ + root: this.tempdir, + name: 'foo', + description: 'bar' + }); + }); + + it('should render a project with app and db', function(done) { + var tempdir = this.tempdir; + + this.project + .renderTemplate(require('../templates/empty'), function (err) { + console.log(fs.readdirSync(tempdir)); + + done(); + }); + }); + }); describe('remove', function () { before(function (done) {