Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

856 lines (733 sloc) 25.171 kb
// Load the basic Geddy toolkit
require('../lib/geddy')
// Dependencies
var fs = require('fs')
, path = require('path')
, fs = require('fs')
, path = require('path')
, utils = require('../lib/utils')
, Adapter = require('../lib/template/adapters')
, getRouterPath
, addRoute
, addSecretData;
getRouterPath = function () {
var beginPath
, jsRouter = path.normalize('config/router.js')
, coffeeRouter = path.normalize('config/router.coffee')
, routerPath;
// Check if the router file exists
beginPath = path.join(process.cwd(), 'config');
utils.file.searchParentPath(jsRouter, function (err) {
if (err) {
var jsErr = err;
// If jsEnvironment wasn't found, try finding coffee variant
utils.file.searchParentPath(coffeeRouter, beginPath, function (err) {
if (err) {
throw jsErr;
} else {
routerPath = coffeeRouter;
}
});
} else {
routerPath = jsRouter;
}
});
return routerPath;
};
addRoute = function (routerPath, newRoute) {
var text = fs.readFileSync(routerPath, 'utf8')
, routerArr;
// Don't add the same route over and over
if (text.indexOf(newRoute) == -1) {
// Add the new resource route just above the export
routerArr = text.split('exports.router');
routerArr[0] += newRoute + '\n';
text = routerArr.join('exports.router');
fs.writeFileSync(routerPath, text, 'utf8');
return true;
}
else {
return false;
}
};
addSecretData = function (obj) {
var dir = process.cwd()
, secretsFile = path.join(dir, 'config', 'secrets.json')
// Mix in the generated app-secret -- should never be checked into revision control
if (utils.file.existsSync(secretsFile)) {
try {
secrets = JSON.parse(fs.readFileSync(secretsFile));
utils.mixin(secrets, obj);
fs.writeFileSync(secretsFile, JSON.stringify(secrets, null, 2));
}
catch (e) {
throw new Error('Could not parse secrets.json file');
}
}
else {
console.log('There is no secrets.json to add config-data to.');
}
};
namespace('env', function () {
task('init', function (environment) {
jake.addListener('complete', function (e) {
jake.Task['env:cleanup'].invoke();
});
geddy.config = require('../lib/config').readConfig({
environment: environment || 'development'
});
geddy.model = require('model');
require('../lib/init').init(geddy, function () {
complete();
});
}, {async: true});
task('cleanup', function () {
// Disconnect all the adapters
var adapters = geddy.model.loadedAdapters
, adapter;
for (var p in adapters) {
adapter = adapters[p];
if (typeof adapter.disconnect == 'function') {
adapter.disconnect();
}
}
});
});
namespace('console', function () {
task('start', ['env:init'], {async: true}, function (environment) {
var env = environment || 'development'
, t = jake.Task['env:init'];
t.addListener('complete', function () {
var repl = require('repl')
, rl;
rl = repl.start({
prompt: '>>> '
, input: process.stdin
, output: process.stdout
});
rl.on('close', function () {
console.log('Exiting...');
return complete();
})
rl.context.capture = function (err, data) {
return rl.context.results = {
err: err
, data: data
};
};
rl.context.echo = function (err, data) {
rl.context.capture(err, data);
if (err) {
console.log('Error: ', err);
}
if (data) {
if (data.length) {
for (var i in data) {
if (data[i] && data[i].toData) {
console.log(data[i].toData());
} else {
console.log(data[i]);
}
}
}
else {
if (data && data.toData) {
console.log(data.toData());
} else {
console.log(data);
}
}
} else {
console.log('No data');
}
};
});
t.invoke(env);
});
});
namespace('db', function () {
task('createTable', ['env:init'], function (name) {
var modelName
, createTable
, adapters
, adapter;
if (typeof name == 'string') {
if (name.indexOf(',') > -1) {
modelNames = name.split(',');
}
else {
modelNames = [name];
}
}
else {
modelNames = name;
}
createTable = function () {
if ((m = modelNames.shift())) {
// Make sure this is a correct model-name
m = utils.string.getInflections(m).constructor.singular;
if (!geddy.model[m]) {
throw new Error(m + ' is not a known model.');
}
adapter = geddy.model.adapters[m];
if (adapter) {
console.log('Creating table for ' + m);
adapter.createTable(m, function (err, data) {
if (err) { throw err }
createTable();
});
}
else {
createTable();
}
}
else {
complete();
}
};
createTable();
}, {async: true});
task('retrofit', ['env:init'], function () {
var modelNames = Object.keys(geddy.model.descriptionRegistry)
, createTask = jake.Task['db:createTable'];
createTask.once('complete', function () {
complete();
});
createTask.invoke(modelNames);
}, {async: true});
});
namespace('gen', function () {
var _writeTemplate = function (name, filename, dirname, opts) {
var options = opts || {}
, names = utils.string.getInflections(name)
, text = fs.readFileSync(path.join(__dirname, filename + '.ejs'), 'utf8').toString()
, bare = options.bare || false // Default to full controller
, adapter
, templContent
, fileDir
, filePath;
// Render with the right model name
adapter = new Adapter({engine: 'ejs', template: text});
templContent = adapter.render({names: names, bare: bare, properties: options.properties});
// Write file
fileDir = dirname;
if (!utils.file.existsSync(fileDir)) {
fs.mkdirSync(fileDir);
}
filePath = path.join(fileDir, names.filename[options.inflection] + '.js');
fs.writeFileSync(filePath, templContent, 'utf8');
console.log('[Added] ' + filePath);
};
var _formatModelProperties = function (properties) {
var obj = {default: {name: '', type: ''}};
if (!properties) {
return obj;
}
obj['default'].name = 'id';
obj['default'].type = 'string';
var itemsArr = properties.split('%')
, name
, type
, args
, i
, value;
i = -1;
while (++i < itemsArr.length) {
value = itemsArr[i];
name = utils.string.camelize(value.replace(/:.*/g, ''));
type = value.replace(/[a-zA-Z]*:?/, '');
args = type.replace(/[a-zA-Z]*:?/, '');
// Take off any args on the type
type = type.replace(/:.*/g, '');
// Defaults and aliases
if (!type) {
type = 'string';
}
if (args === 'def') {
args = 'default';
}
switch (type) {
case 'text':
type = 'string';
break;
case 'integer':
type = 'int';
break;
case 'bool':
type = 'boolean';
break;
case 'default':
case 'def':
type = 'string';
args = 'default';
break;
}
// Manage properties that deal with changing default properties
if (args === 'default') {
// Reset old default property to it's own property, only if it's not
// already the default
if (name !== obj['default'].name) {
// If the new default item already exists then delete it
if (obj[name]) {
delete obj[name];
}
obj[obj['default'].name] = obj[obj['default'].name] || obj['default'];
}
// Add new default property
obj['default'] = {name: name, type: type};
continue;
}
// If ID property is given and it matches the default
// then rewrite the default with the new ID property
if (name === 'id' && obj['default'].name === 'id') {
obj['default'] = {name: name, type: type};
continue;
}
// If the name is name or title then set them to default, otherwise add
// the property normally
if (name === 'name' || name === 'title') {
// Reset old default to it's own property
obj[obj['default'].name] = obj[obj['default'].name] || obj['default'];
// Add new default property
obj['default'] = {name: name, type: type};
} else {
obj[name] = {name: name, type: type};
}
}
return obj;
};
// Creates a new Geddy app scaffold
task('app', function (name, engine, realtime) {
var mkdirs
, cps
, text
, adapter;
if (!name) {
throw new Error('No app name specified.');
}
if (!engine || engine == 'default') {
engine = 'ejs';
}
if (realtime == 'default') {
realtime = false;
}
mkdirs = [
''
, 'config'
, 'app/models'
, 'app/controllers'
, 'app/helpers'
, 'lib'
, 'log'
, 'node_modules'
, 'test'
];
cps = [
(realtime) ? ['realtime/views/' + engine, 'app/views'] : ['views/' + engine, 'app/views']
, ['public', '']
, ['router.js', 'config']
, ['init.js', 'config']
, (realtime) ? ['realtime/environment.js', 'config'] : ['environment.js', 'config']
, ['development.js', 'config']
, ['production.js', 'config']
, ['secrets.json', 'config']
, ['main.js', 'app/controllers']
, ['application.js', 'app/controllers']
, ['favicon.ico', 'public']
, ['gitignore.txt', '.gitignore']
];
mkdirs.forEach(function (dir) {
jake.mkdirP(path.join(name, dir));
});
cps.forEach(function (cp) {
jake.cpR(path.join(__dirname, 'base', cp[0]), path.join(name, cp[1]), {silent: true});
});
// one offs
if (realtime) {
jake.cpR(path.join(__dirname, '..', 'node_modules', 'socket.io' ), path.join(name, 'node_modules'), {silent: true});
}
text = fs.readFileSync(path.join(__dirname, 'base', 'Jakefile.ejs'), 'utf8').toString();
adapter = new Adapter({engine: 'ejs', template: text});
fs.writeFileSync(path.join(name, 'Jakefile'), adapter.render({appName: name}), 'utf8');
console.log('Created app ' + name + '.');
});
// Creates a resource with a model, controller and a resource route
task('resource', function (name, modelProperties) {
var names
, modelTask = jake.Task['gen:model'];
if (!name) {
throw new Error('No resource name specified.');
}
modelTask.on('complete', function () {
jake.Task['gen:controller'].invoke(name);
jake.Task['gen:route'].invoke(name);
names = utils.string.getInflections(name);
// Create views folder but not actions
jake.mkdirP(path.join('app', 'views', names.filename.plural));
console.log('[Added] ' + names.filename.plural + ' view directory');
complete();
});
modelTask.invoke(name, modelProperties);
}, {async: true});
// Creates a full scaffold with views, a model, controller and a resource route
task('scaffold', function (name, realtime, engine, modelProperties) {
var modelTask = jake.Task['gen:model'];
/*if (!modelProperties && engine) {
modelProperties = engine;
engine = '';
}*/
if (!name) {
throw new Error('No scaffold name specified.');
}
if (!engine || engine == 'default') {
engine = 'ejs';
}
if (!realtime || realtime == 'default') {
realtime = false;
}
modelTask.on('complete', function () {
jake.Task['gen:test'].invoke(name,
{properties: modelProperties});
jake.Task['gen:controllerScaffold'].invoke(name,
{properties: modelProperties});
jake.Task['gen:route'].invoke(name);
jake.Task['gen:viewsScaffold'].invoke(name,
{engine: engine, properties: modelProperties, realtime: realtime});
complete();
});
modelTask.invoke(name, modelProperties, 'scaffold');
}, {async: true});
task('model', function (name, properties, modelPath) {
var createTableTask;
if (!name) {
throw new Error('No model name specified.');
}
if (!modelPath) {
modelPath = 'resource';
}
modelPath += '/model';
_writeTemplate(name, modelPath, path.join('app', 'models'), {
inflection: 'singular'
, properties: _formatModelProperties(properties)
});
// Try to create a table -- should be a no-op if an
// appropriate DB adapter can't be found
createTableTask = jake.Task['db:createTable'];
createTableTask.on('complete', function () {
complete();
});
createTableTask.invoke(name);
}, {async: true});
task('controller', function (name) {
if (!name) {
throw new Error('No controller name specified.');
}
_writeTemplate(name, 'resource/controller', path.join('app', 'controllers'),
{inflection: 'plural', bare: false});
});
task('test', function (name) {
if (!name) {
throw new Error('No test name specified.');
}
_writeTemplate(name, 'resource/test', 'test',
{inflection: 'singular'});
});
task('controllerScaffold', function (name, options) {
if (!name) {
throw new Error('No controller name specified.');
}
options = options || {};
_writeTemplate(name, 'scaffold/controller', path.join('app', 'controllers'), {
inflection: 'plural'
, bare: false
, properties: _formatModelProperties(options.properties)
});
});
task('bareController', function (name, engine) {
if (!name) {
throw new Error('No controller name specified.');
}
if (!engine) {
engine = 'ejs';
}
_writeTemplate(name, 'resource/controller', path.join('app', 'controllers'),
{inflection: 'plural', bare: true});
jake.Task['gen:route'].invoke(name, {bare: true});
jake.Task['gen:views'].invoke(name, {bare: true, engine: engine});
});
task('route', function (name, options) {
if (!name) {
throw new Error('No route name specified.');
}
options = options || {};
var names = utils.string.getInflections(name)
, routerPath = getRouterPath()
, routeType = options.bare ? 'Bare' : 'Resource'
, newRoute;
if (routerPath) {
if (routerPath.match('.coffee')) {
if (options.bare) {
newRoute = 'router.match(\'/' + names.filename.plural +
'\').to controller: \'' + names.constructor.plural +
'\', action: \'index\'';
} else {
newRoute = 'router.resource \'' + names.filename.plural + '\'';
}
} else if (routerPath.match('.js')) {
if (options.bare) {
newRoute = 'router.match(\'/' + names.filename.plural +
'\').to({controller: \'' + names.constructor.plural +
'\', action: \'index\'});';
} else {
newRoute = 'router.resource(\'' + names.filename.plural + '\');';
}
}
if (addRoute(routerPath, newRoute)) {
console.log('[Added] ' + routeType + ' ' + names.filename.plural +
' route added to ' + routerPath);
}
else {
console.log(routeType + ' ' + names.filename.plural + ' route already defined in ' +
routerPath);
}
}
else {
console.log('There is no router file to add routes too');
}
});
task('views', function (name, options) {
if (!name) {
throw new Error('No view name specified.');
}
options = options || {};
// Option defaults
options.engine = options.engine || 'ejs';
var names = utils.string.getInflections(name)
, engine = options.engine
, appViewDir = path.join('app', 'views', names.filename.plural)
, templateViewDir = path.join(__dirname, 'views', engine)
, cmds = []
, ext = '.html'
, appLayoutPath
, actions
, addActionView;
// Set extension based on engine option
switch (engine) {
case 'ejs':
ext += '.ejs';
break;
case 'jade':
ext += '.jade';
break;
case 'handlebars':
ext += '.hbs';
break;
case 'mustache':
ext += '.ms';
break;
}
// Set application layout path
appLayoutPath = path.join('app', 'views', 'layouts', 'application');
// Copy template view to application path
addActionView = function (action) {
jake.cpR(path.join(templateViewDir, action + ext), appViewDir);
};
jake.mkdirP(appViewDir);
jake.mkdirP('app/views/layouts');
addActionView('index');
// Add views for the other CRUD actions when doing a full-on resource
if (!options.bare) {
['add', 'edit', 'show'].forEach(function (action) {
addActionView(action);
});
}
// Create default layout if one doesn't exist
// Hack: There should be a better way to detect if a application layout exists
if (!utils.file.existsSync(appLayoutPath + '.html.ejs') && !utils.file.existsSync(appLayoutPath + '.html.jade') &&
!utils.file.existsSync(appLayoutPath + '.html.hbs') && !utils.file.existsSync(appLayoutPath + '.html.ms')) {
// Copy template layout file to apps application layout file
jake.cpR(path.join(templateViewDir, 'layout' + ext), appLayoutPath + ext);
}
console.log('[Added] View templates');
});
task('viewsScaffold', function (name, options) {
if (!name) {
throw new Error('No view name specified.');
}
options = options || {};
// Option defaults
options.engine = options.engine || 'ejs';
var names = utils.string.getInflections(name)
, engine = options.engine
, appViewDir = path.join('app', 'views', names.filename.plural)
, templateViewDir
, cmds = []
, ext = '.html'
, appLayoutPath
, layoutViewPath
, actions
, addActionView
, viewPath
, text
, adapter
, templContent;
// Set extension based on engine option
switch (engine) {
case 'ejs':
ext += '.ejs';
break;
case 'jade':
ext += '.jade';
break;
case 'handlebars':
ext += '.hbs';
break;
case 'mustache':
ext += '.ms';
break;
}
templateViewDir = options.realtime ?
path.join(__dirname, 'scaffold', 'realtime', 'views', engine) :
path.join(__dirname, 'scaffold', 'views', engine);
// Set application layout path
appLayoutPath = path.join('app', 'views', 'layouts', 'application');
// Function to compile the template
addActionView = function (action) {
viewPath = path.join(templateViewDir, action + ext + '.ejs');
if (!utils.file.existsSync(viewPath)) {
return;
}
text = fs.readFileSync(viewPath).toString();
// Compile template text
adapter = new Adapter({engine: 'ejs', template: text});
templContent = adapter.render({names: names, properties: _formatModelProperties(options.properties)});
// Write file
fs.writeFileSync(path.join(appViewDir, action + ext),
templContent.replace(/<@/g, '<%').replace(/@>/g, '%>'), 'utf8');
};
jake.mkdirP(appViewDir);
jake.mkdirP('app/views/layouts');
// Add views for the other CRUD actions when doing a full-on resource
['index', 'add', 'show', 'edit', 'form'].forEach(function (action) {
addActionView(action);
});
// Create default layout if one doesn't exist
// Hack: There should be a better way to detect if a application layout exists
if (!utils.file.existsSync(appLayoutPath + '.html.ejs') && !utils.file.existsSync(appLayoutPath + '.html.jade') &&
!utils.file.existsSync(appLayoutPath + '.html.hbs') && !utils.file.existsSync(appLayoutPath + '.html.ms')) {
// Copy template layout file to apps application layout file
jake.cpR(path.join(templateViewDir, 'layout' + ext), appLayoutPath + ext);
}
console.log('[Added] View templates');
});
// Generate a new application secret in environment.js
task('secret', function () {
var secret = utils.string.uuid(128);
addSecretData({
secret: secret
});
console.log('Added app-secret to config/secrets.json.\n' +
'DO NOT add this file into your revision control.\n' +
'DO make a backup of it, keep it someplace safe.');
});
});
namespace('auth', function () {
task('update', {async: true}, function () {
// FIXME: Pull these version numbers out of geddy-passport's package.json
var cwd = process.cwd()
, packages = 'geddy-passport@0.0.x'
, updatePath = path.join('geddy-passport', 'app',
'helpers', 'passport');
console.log('Updating helper from ' + packages + '...');
jake.exec('npm uninstall ' + packages +
' && npm install ' + packages, function () {
var from = path.join(cwd, 'node_modules', updatePath)
, to = path.join(cwd, 'app', 'helpers');
jake.rmRf(path.join(to, 'passport'), {silent: true});
jake.cpR(from, to, {silent: true});
console.log('Updated helper from ' + packages);
console.log('Cleaning up...');
jake.exec('npm uninstall geddy-passport', function () {
complete();
});
}, {printStdout: true});
});
task('init', {async: true}, function () {
var go = false
// FIXME: Pull these version numbers out of geddy-passport's package.json
, packages = 'geddy-passport@0.0.x passport@0.1.x passport-local@0.1.x ' +
'passport-facebook@0.1.x passport-twitter@0.1.x passport-yammer@0.1.x'
, readline = require('readline')
, rl = readline.createInterface({
input: process.stdin
, output: process.stdout
})
, toBase = process.cwd()
, fromBase = path.join(toBase, 'node_modules', 'geddy-passport');
rl.setPrompt('This command will create/overwrite files in your app.\n' +
'Do you wish to continue? (yes|no)\n');
rl.prompt();
rl.addListener('line', function (line) {
if (line == 'yes') {
go = true;
}
rl.close();
});
rl.addListener('close', function () {
if (go) {
console.log('Installing ' + packages + '...');
jake.exec('npm uninstall ' + packages +
' && npm install ' + packages, function () {
var list = fs.readFileSync(path.join(fromBase, 'file_list.json'))
, routerPath = getRouterPath()
, newRoute;
list = JSON.parse(list.toString());
list.forEach(function (item) {
var from = path.join(fromBase, item)
, to = path.dirname(path.join(toBase, item));
jake.mkdirP(to);
console.log('Creating file: ' + item);
jake.cpR(from, to, {silent: true});
});
if (routerPath) {
if (routerPath.match('.coffee')) {
throw new Error(
'Geddy passport integration does not support CoffeeScript.');
}
else {
newRoute = "router.get('/login').to('Main.login');\n" +
"router.get('/logout').to('Main.logout');\n" +
"router.post('/auth/local').to('Auth.local');\n" +
"router.get('/auth/twitter').to('Auth.twitter');\n" +
"router.get('/auth/twitter/callback').to('Auth.twitterCallback');\n" +
"router.get('/auth/facebook').to('Auth.facebook');\n" +
"router.get('/auth/facebook/callback').to('Auth.facebookCallback');\n" +
"router.get('/auth/yammer').to('Auth.yammer');\n" +
"router.get('/auth/yammer/callback').to('Auth.yammerCallback');\n" +
"router.resource('users');";
if (addRoute(routerPath, newRoute)) {
console.log('Added authentication routes:\n' + newRoute);
}
else {
console.log('Authentication routes already defined in ' +
routerPath);
}
}
}
else {
console.log('There is no router file to add routes too');
}
console.log('Creating secrets.json file with stubbed-out Passport config.');
jake.cpR(path.join(fromBase, 'config', 'secrets.json.template'),
path.join(toBase, 'config', 'secrets.json'), {silent: true});
jake.Task['gen:secret'].invoke();
console.log('Cleaning up...');
jake.exec('npm uninstall geddy-passport', function () {
console.log('Please set up your Passport config in config/secrets.json');
complete();
});
}, {printStdout: true});
}
});
});
});
Jump to Line
Something went wrong with that request. Please try again.