Release 2.0.0beta2
[express.git] / bin / express
blobe1d4a44475d9a1624b9a3f87d3cb0a9cf086fa98
1 #!/usr/bin/env node
3 /**
4  * Module dependencies.
5  */
7 var fs = require('fs')
8   , exec = require('child_process').exec;
10 /**
11  * Framework version.
12  */
14 var version = '2.0.0beta2';
16 /**
17  * Add session support.
18  */
20 var sessions = false;
22 /**
23  * CSS engine to utilize.
24  */
26 var cssEngine;
28 /**
29  * Template engine to utilize.
30  */
32 var templateEngine = 'jade';
34 /**
35  * Usage documentation.
36  */
38 var usage = ''
39   + '\x1b[1mUsage\x1b[0m: express [options] [PATH]\n'
40   + '\n'
41   + '\x1b[1mOptions\x1b[0m:\n'
42   + '  -s, --sessions         Add session support\n'
43   + '  -t, --template ENGINE  Add template ENGINE support (jade|ejs). Defaults to jade\n'
44   + '  -c, --css ENGINE       Add stylesheet ENGINE support (less|sass|stylus). Defaults to plain css\n'
45   + '  -v, --version          Output framework version\n'
46   + '  -h, --help             Output help information\n'
47   ;
49 /**
50  * Jade layout template.
51  */
53 var jadeLayout = [
54     '!!!'
55   , 'html'
56   , '  head'
57   , '    title= title'
58   , '    link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
59   , '  body!= body'
60 ].join('\n');
62 /**
63  * Jade index template.
64  */
66 var jadeIndex = [
67     'h1= title'
68   , 'p Welcome to #{title}'
69 ].join('\n');
71 /**
72  * EJS layout template.
73  */
75 var ejsLayout = [
76     '<!DOCTYPE html>'
77   , '<html>'
78   , '  <head>'
79   , '    <title><%= title %></title>'
80   , '    <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
81   , '  </head>'
82   , '  <body>'
83   , '    <%- body %>'
84   , '  </body>'
85   , '</html>'
86 ].join('\n');
88 /**
89  * EJS index template.
90  */
92 var ejsIndex = [
93     '<h1><%= title %></h1>'
94   , '<p>Welcome to <%= title %></p>'
95   ].join('\n');
97 /**
98  * Default css template.
99  */
101 var css = [
102     'body {'
103   , '  padding: 50px;'
104   , '  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
105   , '}'
106   , ''
107   , 'a {'
108   , '  color: #00B7FF;'
109   , '}'
110 ].join('\n');
113  * Default less template.
114  */
116 var less = [
117     'body {'
118   , '  padding: 50px;'
119   , '  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
120   , '}'
121   , ''
122   , 'a {'
123   , '  color: #00B7FF;'
124   , '}'
125 ].join('\n');
128  * Default sass template.
129  */
131 var sass = [
132     'body'
133   , '  :padding 50px'
134   , '  :font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
135   , 'a'
136   , '  :color #00B7FF'
137 ].join('\n');
140  * Default stylus template.
141  */
143 var stylus = [
144     'body'
145   , '  padding 50px'
146   , '  font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
147   , 'a'
148   , '  color #00B7FF'
149 ].join('\n');
152  * App test template.
153  */
155 var appTest = [
156     ""
157   , "// Run $ expresso"
158   , ""
159   , "/**"
160   , " * Module dependencies."
161   , " */"
162   , ""
163   , "var app = require('../app')"
164   , "  , assert = require('assert');"
165   , "",
166   , "module.exports = {"
167   , "  'GET /': function(){"
168   , "    assert.response(app,"
169   , "      { url: '/' },"
170   , "      { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }},"
171   , "      function(res){"
172   , "        assert.includes(res.body, '<title>Express</title>');"
173   , "      });"
174   , "  }"
175   , "};"
176 ].join('\n');
179  * App template.
180  */
182 var app = [
183     ''
184   , '/**'
185   , ' * Module dependencies.'
186   , ' */'
187   , ''
188   , 'var express = require(\'express\');'
189   , ''
190   , 'var app = module.exports = express.createServer();'
191   , ''
192   , '// Configuration'
193   , ''
194   , 'app.configure(function(){'
195   , '  app.set(\'views\', __dirname + \'/views\');'
196   , '  app.set(\'view engine\', \':TEMPLATE\');'
197   , '  app.use(express.bodyParser());'
198   , '  app.use(express.methodOverride());{sess}{css}'
199   , '  app.use(app.router);'
200   , '  app.use(express.static(__dirname + \'/public\'));'
201   , '});'
202   , ''
203   , 'app.configure(\'development\', function(){'
204   , '  app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); '
205   , '});'
206   , ''
207   , 'app.configure(\'production\', function(){'
208   , '  app.use(express.errorHandler()); '
209   , '});'
210   , ''
211   , '// Routes'
212   , ''
213   , 'app.get(\'/\', function(req, res){'
214   , '  res.render(\'index\', {'
215   , '    title: \'Express\''
216   , '  });'
217   , '});'
218   , ''
219   , '// Only listen on $ node app.js'
220   , ''
221   , 'if (!module.parent) {'
222   , '  app.listen(3000);'
223   , '  console.log("Express server listening on port %d", app.address().port)'
224   , '}'
225   , ''
226 ].join('\n');
228 // Parse arguments
230 var args = process.argv.slice(2)
231   , path = '.';
233 while (args.length) {
234   var arg = args.shift();
235   switch (arg) {
236     case '-h':
237     case '--help':
238       abort(usage);
239       break;
240     case '-v':
241     case '--version':
242       abort(version);
243       break;
244     case '-s':
245     case '--session':
246     case '--sessions':
247       sessions = true;
248       break;
249     case '-c':
250     case '--css':
251       args.length
252         ? (cssEngine = args.shift())
253         : abort('--css requires an argument');
254       break;
255     case '-t':
256     case '--template':
257       args.length
258         ? (templateEngine = args.shift())
259         : abort('--template requires an argument');
260       break;
261     default:
262         path = arg;
263   }
266 // Generate application
268 (function createApplication(path) {
269   emptyDirectory(path, function(empty){
270     if (empty) {
271       createApplicationAt(path);
272     } else {
273       confirm('destination is not empty, continue? ', function(ok){
274         if (ok) {
275           process.stdin.destroy();
276           createApplicationAt(path);
277         } else {
278           abort('aborting');
279         }
280       });
281     }
282   });
283 })(path);
286  * Create application at the given directory `path`.
288  * @param {String} path
289  */
291 function createApplicationAt(path) {
292   mkdir(path, function(){
293     mkdir(path + '/pids');
294     mkdir(path + '/logs');
295     mkdir(path + '/public/javascripts');
296     mkdir(path + '/public/images');
297     mkdir(path + '/public/stylesheets', function(){
298       switch (cssEngine) {
299         case 'stylus':
300           write(path + '/public/stylesheets/style.styl', stylus);
301           break;
302         case 'less':
303           write(path + '/public/stylesheets/style.less', less);
304           break;
305         case 'sass':
306           write(path + '/public/stylesheets/style.sass', sass);
307           break;
308         default:
309           write(path + '/public/stylesheets/style.css', css);
310       }
311     });
312     mkdir(path + '/views', function(){
313       switch (templateEngine) {
314         case 'ejs':
315           write(path + '/views/layout.ejs', ejsLayout);
316           write(path + '/views/index.ejs', ejsIndex);
317           break;
318         case 'jade':
319           write(path + '/views/layout.jade', jadeLayout);
320           write(path + '/views/index.jade', jadeIndex);
321           break;
322       }
323     });
324     mkdir(path + '/test', function(){
325       write(path + '/test/app.test.js', appTest);
326     });
328     // CSS Engine support
329     switch (cssEngine) {
330       case 'sass':
331       case 'less':
332         app = app.replace('{css}', '\n  app.use(express.compiler({ src: __dirname + \'/public\', enable: [\'' + cssEngine + '\'] }));');
333         break;
334       case 'stylus':
335         app = app.replace('{css}', '\n  app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));');
336         break;
337       default:
338         app = app.replace('{css}', '');
339     }
341     // Session support
342     app = app.replace('{sess}', sessions
343       ? '\n  app.use(express.cookieParser());\n  app.use(express.session({ secret: \'your secret here\' }));'
344       : '');
346     // Template support
347     app = app.replace(':TEMPLATE', templateEngine);
349     write(path + '/app.js', app);
351     // Suggestions
352     process.on('exit', function(){
353       if (cssEngine) {
354         console.log('   - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
355           , cssEngine
356           , cssEngine);
357       }
358       console.log('   - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
359         , templateEngine
360         , templateEngine);
361     });
362   });
366  * Check if the given directory `path` is empty.
368  * @param {String} path
369  * @param {Function} fn
370  */
372 function emptyDirectory(path, fn) {
373   fs.readdir(path, function(err, files){
374     if (err && 'ENOENT' != err.code) throw err;
375     fn(!files || !files.length);
376   });
380  * echo str > path.
382  * @param {String} path
383  * @param {String} str
384  */
386 function write(path, str) {
387   fs.writeFile(path, str);
388   console.log('   \x1b[36mcreate\x1b[0m : ' + path);
392  * Prompt confirmation with the given `msg`.
394  * @param {String} msg
395  * @param {Function} fn
396  */
398 function confirm(msg, fn) {
399   prompt(msg, function(val){
400     fn(/^ *y(es)?/i.test(val));
401   });
405  * Prompt input with the given `msg` and callback `fn`.
407  * @param {String} msg
408  * @param {Function} fn
409  */
411 function prompt(msg, fn) {
412   // prompt
413   if (' ' == msg[msg.length - 1]) {
414     process.stdout.write(msg);
415   } else {
416     console.log(msg);
417   }
419   // stdin
420   process.stdin.setEncoding('ascii');
421   process.stdin.once('data', function(data){
422     fn(data);
423   }).resume();
427  * Mkdir -p.
429  * @param {String} path
430  * @param {Function} fn
431  */
433 function mkdir(path, fn) {
434   exec('mkdir -p ' + path, function(err){
435     if (err) throw err;
436     console.log('   \x1b[36mcreate\x1b[0m : ' + path);
437     fn && fn();
438   });
442  * Exit with the given `str`.
444  * @param {String} str
445  */
447 function abort(str) {
448   console.error(str);
449   process.exit(1);