#!/usr/bin/env node /** * Module dependencies. */ var fs = require('fs') , exec = require('child_process').exec; /** * Framework version. */ var version = '2.3.3'; /** * Add session support. */ var sessions = false; /** * CSS engine to utilize. */ var cssEngine; /** * Template engine to utilize. */ var templateEngine = 'jade'; /** * Usage documentation. */ var usage = '' + '\n' + ' Usage: express [options] [path]\n' + '\n' + ' Options:\n' + ' -s, --sessions add session support\n' + ' -t, --template add template support (jade|ejs). default=jade\n' + ' -c, --css add stylesheet support (less|sass|stylus). default=plain css\n' + ' -v, --version output framework version\n' + ' -h, --help output help information\n' ; /** * Jade layout template. */ var jadeLayout = [ '!!!' , 'html' , ' head' , ' title= title' , ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')' , ' body!= body' ].join('\n'); /** * Jade index template. */ var jadeIndex = [ 'h1= title' , 'p Welcome to #{title}' ].join('\n'); /** * EJS layout template. */ var ejsLayout = [ '' , '' , ' ' , ' <%= title %>' , ' ' , ' ' , ' ' , ' <%- body %>' , ' ' , '' ].join('\n'); /** * EJS index template. */ var ejsIndex = [ '

<%= title %>

' , '

Welcome to <%= title %>

' ].join('\n'); /** * Default css template. */ var css = [ 'body {' , ' padding: 50px;' , ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;' , '}' , '' , 'a {' , ' color: #00B7FF;' , '}' ].join('\n'); /** * Default less template. */ var less = [ 'body {' , ' padding: 50px;' , ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;' , '}' , '' , 'a {' , ' color: #00B7FF;' , '}' ].join('\n'); /** * Default sass template. */ var sass = [ 'body' , ' :padding 50px' , ' :font 14px "Lucida Grande", Helvetica, Arial, sans-serif' , 'a' , ' :color #00B7FF' ].join('\n'); /** * Default stylus template. */ var stylus = [ 'body' , ' padding 50px' , ' font 14px "Lucida Grande", Helvetica, Arial, sans-serif' , 'a' , ' color #00B7FF' ].join('\n'); /** * App test template. */ var appTest = [ "" , "// Run $ expresso" , "" , "/**" , " * Module dependencies." , " */" , "" , "var app = require('../app')" , " , assert = require('assert');" , "", , "module.exports = {" , " 'GET /': function(){" , " assert.response(app," , " { url: '/' }," , " { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }}," , " function(res){" , " assert.includes(res.body, 'Express');" , " });" , " }" , "};" ].join('\n'); /** * App template. */ var app = [ '' , '/**' , ' * Module dependencies.' , ' */' , '' , 'var express = require(\'express\');' , '' , 'var app = module.exports = express.createServer();' , '' , '// Configuration' , '' , 'app.configure(function(){' , ' app.set(\'views\', __dirname + \'/views\');' , ' app.set(\'view engine\', \':TEMPLATE\');' , ' app.use(express.bodyParser());' , ' app.use(express.methodOverride());{sess}{css}' , ' app.use(app.router);' , ' app.use(express.static(__dirname + \'/public\'));' , '});' , '' , 'app.configure(\'development\', function(){' , ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); ' , '});' , '' , 'app.configure(\'production\', function(){' , ' app.use(express.errorHandler()); ' , '});' , '' , '// Routes' , '' , 'app.get(\'/\', function(req, res){' , ' res.render(\'index\', {' , ' title: \'Express\'' , ' });' , '});' , '' , '// Only listen on $ node app.js' , '' , 'if (!module.parent) {' , ' app.listen(3000);' , ' console.log("Express server listening on port %d", app.address().port);' , '}' , '' ].join('\n'); // Parse arguments var args = process.argv.slice(2) , path = '.'; while (args.length) { var arg = args.shift(); switch (arg) { case '-h': case '--help': abort(usage); break; case '-v': case '--version': abort(version); break; case '-s': case '--session': case '--sessions': sessions = true; break; case '-c': case '--css': args.length ? (cssEngine = args.shift()) : abort('--css requires an argument'); break; case '-t': case '--template': args.length ? (templateEngine = args.shift()) : abort('--template requires an argument'); break; default: path = arg; } } // Generate application (function createApplication(path) { emptyDirectory(path, function(empty){ if (empty) { createApplicationAt(path); } else { confirm('destination is not empty, continue? ', function(ok){ if (ok) { process.stdin.destroy(); createApplicationAt(path); } else { abort('aborting'); } }); } }); })(path); /** * Create application at the given directory `path`. * * @param {String} path */ function createApplicationAt(path) { mkdir(path, function(){ mkdir(path + '/pids'); mkdir(path + '/logs'); mkdir(path + '/public/javascripts'); mkdir(path + '/public/images'); mkdir(path + '/public/stylesheets', function(){ switch (cssEngine) { case 'stylus': write(path + '/public/stylesheets/style.styl', stylus); break; case 'less': write(path + '/public/stylesheets/style.less', less); break; case 'sass': write(path + '/public/stylesheets/style.sass', sass); break; default: write(path + '/public/stylesheets/style.css', css); } }); mkdir(path + '/views', function(){ switch (templateEngine) { case 'ejs': write(path + '/views/layout.ejs', ejsLayout); write(path + '/views/index.ejs', ejsIndex); break; case 'jade': write(path + '/views/layout.jade', jadeLayout); write(path + '/views/index.jade', jadeIndex); break; } }); mkdir(path + '/test', function(){ write(path + '/test/app.test.js', appTest); }); // CSS Engine support switch (cssEngine) { case 'sass': case 'less': app = app.replace('{css}', '\n app.use(express.compiler({ src: __dirname + \'/public\', enable: [\'' + cssEngine + '\'] }));'); break; case 'stylus': app = app.replace('{css}', '\n app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));'); break; default: app = app.replace('{css}', ''); } // Session support app = app.replace('{sess}', sessions ? '\n app.use(express.cookieParser());\n app.use(express.session({ secret: \'your secret here\' }));' : ''); // Template support app = app.replace(':TEMPLATE', templateEngine); write(path + '/app.js', app); // Suggestions process.on('exit', function(){ if (cssEngine) { console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m' , cssEngine , cssEngine); } console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m' , templateEngine , templateEngine); }); }); } /** * Check if the given directory `path` is empty. * * @param {String} path * @param {Function} fn */ function emptyDirectory(path, fn) { fs.readdir(path, function(err, files){ if (err && 'ENOENT' != err.code) throw err; fn(!files || !files.length); }); } /** * echo str > path. * * @param {String} path * @param {String} str */ function write(path, str) { fs.writeFile(path, str); console.log(' \x1b[36mcreate\x1b[0m : ' + path); } /** * Prompt confirmation with the given `msg`. * * @param {String} msg * @param {Function} fn */ function confirm(msg, fn) { prompt(msg, function(val){ fn(/^ *y(es)?/i.test(val)); }); } /** * Prompt input with the given `msg` and callback `fn`. * * @param {String} msg * @param {Function} fn */ function prompt(msg, fn) { // prompt if (' ' == msg[msg.length - 1]) { process.stdout.write(msg); } else { console.log(msg); } // stdin process.stdin.setEncoding('ascii'); process.stdin.once('data', function(data){ fn(data); }).resume(); } /** * Mkdir -p. * * @param {String} path * @param {Function} fn */ function mkdir(path, fn) { exec('mkdir -p ' + path, function(err){ if (err) throw err; console.log(' \x1b[36mcreate\x1b[0m : ' + path); fn && fn(); }); } /** * Exit with the given `str`. * * @param {String} str */ function abort(str) { console.error(str); process.exit(1); }