From 759b67816b888c2a13128106ea3c1a94791169f1 Mon Sep 17 00:00:00 2001 From: Eliran Pe'er Date: Sat, 21 Oct 2017 14:07:44 +0300 Subject: [PATCH 1/4] Adds the ability to generate a project using ES2015 template --- bin/express-cli.js | 58 ++++++++++------- package.json | 1 + templates/js/es2015/app.js.ejs | 54 ++++++++++++++++ templates/js/es2015/routes/index.js | 9 +++ templates/js/es2015/routes/users.js | 9 +++ templates/js/es2015/www.ejs | 90 ++++++++++++++++++++++++++ templates/js/{ => es5}/app.js.ejs | 0 templates/js/{ => es5}/routes/index.js | 0 templates/js/{ => es5}/routes/users.js | 0 templates/js/{ => es5}/www.ejs | 0 10 files changed, 196 insertions(+), 25 deletions(-) create mode 100644 templates/js/es2015/app.js.ejs create mode 100644 templates/js/es2015/routes/index.js create mode 100644 templates/js/es2015/routes/users.js create mode 100644 templates/js/es2015/www.ejs rename templates/js/{ => es5}/app.js.ejs (100%) rename templates/js/{ => es5}/routes/index.js (100%) rename templates/js/{ => es5}/routes/users.js (100%) rename templates/js/{ => es5}/www.ejs (100%) diff --git a/bin/express-cli.js b/bin/express-cli.js index 63fc0903..55e39aed 100755 --- a/bin/express-cli.js +++ b/bin/express-cli.js @@ -9,6 +9,7 @@ var program = require('commander') var readline = require('readline') var sortedObject = require('sorted-object') var util = require('util') +var detect = require('feature-detect-es6') var MODE_0666 = parseInt('0666', 8) var MODE_0755 = parseInt('0755', 8) @@ -58,8 +59,15 @@ program .option('-c, --css ', 'add stylesheet support (less|stylus|compass|sass) (defaults to plain css)') .option(' --git', 'add .gitignore') .option('-f, --force', 'force on non-empty directory') + .option(' --es2015', 'use ES2015 standards') + .option(' --es5', 'use ES5 standards') .parse(process.argv) +var esVersion = program.es5 ? 'es5' + : program.es2015 ? 'es2015' + : detect.all('arrowFunction', 'let') ? 'es2015' + : 'es5' + if (!exit.exited) { main() } @@ -68,7 +76,7 @@ if (!exit.exited) { * Install an around function; AOP. */ -function around (obj, method, fn) { +function around(obj, method, fn) { var old = obj[method] obj[method] = function () { @@ -82,7 +90,7 @@ function around (obj, method, fn) { * Install a before function; AOP. */ -function before (obj, method, fn) { +function before(obj, method, fn) { var old = obj[method] obj[method] = function () { @@ -95,7 +103,7 @@ function before (obj, method, fn) { * Prompt for confirmation on STDOUT/STDIN */ -function confirm (msg, callback) { +function confirm(msg, callback) { var rl = readline.createInterface({ input: process.stdin, output: process.stdout @@ -111,7 +119,7 @@ function confirm (msg, callback) { * Copy file from template directory. */ -function copyTemplate (from, to) { +function copyTemplate(from, to) { write(to, fs.readFileSync(path.join(TEMPLATE_DIR, from), 'utf-8')) } @@ -119,12 +127,12 @@ function copyTemplate (from, to) { * Copy multiple files from template directory. */ -function copyTemplateMulti (fromDir, toDir, nameGlob) { +function copyTemplateMulti(fromDir, toDir, nameGlob) { fs.readdirSync(path.join(TEMPLATE_DIR, fromDir)) - .filter(minimatch.filter(nameGlob, { matchBase: true })) - .forEach(function (name) { - copyTemplate(path.join(fromDir, name), path.join(toDir, name)) - }) + .filter(minimatch.filter(nameGlob, { matchBase: true })) + .forEach(function (name) { + copyTemplate(path.join(fromDir, name), path.join(toDir, name)) + }) } /** @@ -134,12 +142,12 @@ function copyTemplateMulti (fromDir, toDir, nameGlob) { * @param {string} dir */ -function createApplication (name, dir) { +function createApplication(name, dir) { console.log() // JavaScript - var app = loadTemplate('js/app.js') - var www = loadTemplate('js/www') + var app = loadTemplate('js/' + esVersion + '/app.js') + var www = loadTemplate('js/' + esVersion + '/www') // App name www.locals.name = name @@ -178,7 +186,7 @@ function createApplication (name, dir) { // copy route templates mkdir(dir, 'routes') - copyTemplateMulti('js/routes', dir + '/routes', '*.js') + copyTemplateMulti('js/' + esVersion + '/routes', dir + '/routes', '*.js') // copy view templates mkdir(dir, 'views') @@ -347,7 +355,7 @@ function createApplication (name, dir) { * @param {String} pathName */ -function createAppName (pathName) { +function createAppName(pathName) { return path.basename(pathName) .replace(/[^A-Za-z0-9.()!~*'-]+/g, '-') .replace(/^[-_.]+|-+$/g, '') @@ -361,7 +369,7 @@ function createAppName (pathName) { * @param {Function} fn */ -function emptyDirectory (path, fn) { +function emptyDirectory(path, fn) { fs.readdir(path, function (err, files) { if (err && err.code !== 'ENOENT') throw err fn(!files || !files.length) @@ -372,11 +380,11 @@ function emptyDirectory (path, fn) { * Graceful exit for async STDIO */ -function exit (code) { +function exit(code) { // flush output for Node.js Windows pipe bug // https://github.com/joyent/node/issues/6247 is just one bug example // https://github.com/visionmedia/mocha/issues/333 has a good discussion - function done () { + function done() { if (!(draining--)) _exit(code) } @@ -398,7 +406,7 @@ function exit (code) { * Determine if launched from cmd.exe */ -function launchedFromCmd () { +function launchedFromCmd() { return process.platform === 'win32' && process.env._ === undefined } @@ -407,11 +415,11 @@ function launchedFromCmd () { * Load template file. */ -function loadTemplate (name) { +function loadTemplate(name) { var contents = fs.readFileSync(path.join(__dirname, '..', 'templates', (name + '.ejs')), 'utf-8') var locals = Object.create(null) - function render () { + function render() { return ejs.render(contents, locals) } @@ -425,7 +433,7 @@ function loadTemplate (name) { * Main program. */ -function main () { +function main() { // Path var destinationPath = program.args.shift() || '.' @@ -472,7 +480,7 @@ function main () { * @param {string} dir */ -function mkdir (base, dir) { +function mkdir(base, dir) { var loc = path.join(base, dir) console.log(' \x1b[36mcreate\x1b[0m : ' + loc + path.sep) @@ -486,7 +494,7 @@ function mkdir (base, dir) { * @param {String} newName */ -function renamedOption (originalName, newName) { +function renamedOption(originalName, newName) { return function (val) { warning(util.format("option `%s' has been renamed to `%s'", originalName, newName)) return val @@ -499,7 +507,7 @@ function renamedOption (originalName, newName) { * @param {String} message */ -function warning (message) { +function warning(message) { console.error() message.split('\n').forEach(function (line) { console.error(' warning: %s', line) @@ -514,7 +522,7 @@ function warning (message) { * @param {String} str */ -function write (path, str, mode) { +function write(path, str, mode) { fs.writeFileSync(path, str, { mode: mode || MODE_0666 }) console.log(' \x1b[36mcreate\x1b[0m : ' + path) } diff --git a/package.json b/package.json index f72e58c1..179fec32 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "commander": "2.11.0", "ejs": "2.5.7", "minimatch": "3.0.4", + "feature-detect-es6": "^1.3.1", "mkdirp": "0.5.1", "sorted-object": "2.0.1" }, diff --git a/templates/js/es2015/app.js.ejs b/templates/js/es2015/app.js.ejs new file mode 100644 index 00000000..7171738d --- /dev/null +++ b/templates/js/es2015/app.js.ejs @@ -0,0 +1,54 @@ +const express = require('express'); +const path = require('path'); +const favicon = require('serve-favicon'); +const logger = require('morgan'); +const cookieParser = require('cookie-parser'); +<% Object.keys(modules).forEach(function (variable) { -%> + const <%- variable %> = require('<%- modules[variable] %>'); +<% }); -%> + +const index = require('./routes/index'); +const users = require('./routes/users'); + +const app = express(); + +// view engine setup +<% if (view.render) { -%> +app.engine('<%- view.engine %>', <%- view.render %>); +<% } -%> +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', '<%- view.engine %>'); + +// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +<% uses.forEach(function (use) { -%> +app.use(<%- use %>); +<% }); -%> +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', index); +app.use('/users', users); + +// catch 404 and forward to error handler +app.use((req, res, next) => { + const err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handler +app.use((err, req, res, next) => { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +module.exports = app; diff --git a/templates/js/es2015/routes/index.js b/templates/js/es2015/routes/index.js new file mode 100644 index 00000000..f2d4e52a --- /dev/null +++ b/templates/js/es2015/routes/index.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); + +/* GET home page. */ +router.get('/', (req, res, next) => { + res.render('index', { title: 'Express' }); +}); + +module.exports = router; diff --git a/templates/js/es2015/routes/users.js b/templates/js/es2015/routes/users.js new file mode 100644 index 00000000..a1cebe1b --- /dev/null +++ b/templates/js/es2015/routes/users.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); + +/* GET users listing. */ +router.get('/', (req, res, next) => { + res.send('respond with a resource'); +}); + +module.exports = router; diff --git a/templates/js/es2015/www.ejs b/templates/js/es2015/www.ejs new file mode 100644 index 00000000..5f697b5f --- /dev/null +++ b/templates/js/es2015/www.ejs @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +const app = require('../app'); +const debug = require('debug')('<%- name %>:server'); +const http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +const port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + + const server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + const port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + const addr = server.address(); + const bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/templates/js/app.js.ejs b/templates/js/es5/app.js.ejs similarity index 100% rename from templates/js/app.js.ejs rename to templates/js/es5/app.js.ejs diff --git a/templates/js/routes/index.js b/templates/js/es5/routes/index.js similarity index 100% rename from templates/js/routes/index.js rename to templates/js/es5/routes/index.js diff --git a/templates/js/routes/users.js b/templates/js/es5/routes/users.js similarity index 100% rename from templates/js/routes/users.js rename to templates/js/es5/routes/users.js diff --git a/templates/js/www.ejs b/templates/js/es5/www.ejs similarity index 100% rename from templates/js/www.ejs rename to templates/js/es5/www.ejs From 071470d2c9cd8d55abf2e72029a5437a8ef47b5f Mon Sep 17 00:00:00 2001 From: Eliran Pe'er Date: Sat, 21 Oct 2017 14:30:45 +0300 Subject: [PATCH 2/4] add tests for --es2015 and --es5 options --- test/cmd.js | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/test/cmd.js b/test/cmd.js index 0e5108f7..d0cbdce8 100644 --- a/test/cmd.js +++ b/test/cmd.js @@ -1014,6 +1014,128 @@ describe('express(1)', function () { }) }) }) + + describe('es2015', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with vash templates', function (done) { + run(ctx.dir, ['--es2015'], function (err, stdout) { + if (err) return done(err) + ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 16) + done() + }) + }) + + it('should have "const" instead of "var" in every JavaScript file', function () { + ['bin/www', 'routes/index.js', 'routes/users.js'].forEach(function (fileName) { + var filePath = path.resolve(ctx.dir, fileName) + var contents = fs.readFileSync(filePath, 'utf8') + + assert.equal(contents.indexOf('var'), -1) + assert.notEqual(contents.indexOf('const'), -1) + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + describe('npm start', function () { + before('start app', function () { + this.app = new AppRunner(ctx.dir) + }) + + after('stop app', function (done) { + this.app.stop(done) + }) + + it('should start app', function (done) { + this.timeout(5000) + this.app.start(done) + }) + + it('should respond to HTTP request', function (done) { + request(this.app) + .get('/') + .expect(200, /Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + request(this.app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + }) + + describe('es5', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with vash templates', function (done) { + run(ctx.dir, ['--es5'], function (err, stdout) { + if (err) return done(err) + ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 16) + done() + }) + }) + + it('should have "var" and should not have "const" in every JavaScript file', function () { + ['bin/www', 'routes/index.js', 'routes/users.js'].forEach(function (fileName) { + var filePath = path.resolve(ctx.dir, fileName) + var contents = fs.readFileSync(filePath, 'utf8') + + assert.notEqual(contents.indexOf('var'), -1) + assert.equal(contents.indexOf('const'), -1) + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + describe('npm start', function () { + before('start app', function () { + this.app = new AppRunner(ctx.dir) + }) + + after('stop app', function (done) { + this.app.stop(done) + }) + + it('should start app', function (done) { + this.timeout(5000) + this.app.start(done) + }) + + it('should respond to HTTP request', function (done) { + request(this.app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + request(this.app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + }) }) }) From 7f4713e610d246492a4e2879a059e344fb18e360 Mon Sep 17 00:00:00 2001 From: Eliran Pe'er <eliran013@gmail.com> Date: Sat, 21 Oct 2017 14:43:48 +0300 Subject: [PATCH 3/4] Adds --es5 and --es2015 flags to documentation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index edc7efa8..800d3552 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ This generator can also be further configured with the following command line fl -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css) --git add .gitignore -f, --force force on non-empty directory + --es2015 use ES2015 standards + --es5 use ES5 standards -h, --help output usage information ## License From c0edf63d7b71bf47d402ce91fcc697efe1505cc2 Mon Sep 17 00:00:00 2001 From: Eliran Pe'er <eliran013@gmail.com> Date: Sat, 28 Oct 2017 20:05:10 +0300 Subject: [PATCH 4/4] fixing linting issues --- bin/express-cli.js | 38 +++++++++++++++++++------------------- package.json | 2 +- test/cmd.js | 6 +++--- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bin/express-cli.js b/bin/express-cli.js index 55e39aed..b0d3290d 100755 --- a/bin/express-cli.js +++ b/bin/express-cli.js @@ -1,5 +1,6 @@ #!/usr/bin/env node +var detect = require('feature-detect-es6') var ejs = require('ejs') var fs = require('fs') var minimatch = require('minimatch') @@ -9,7 +10,6 @@ var program = require('commander') var readline = require('readline') var sortedObject = require('sorted-object') var util = require('util') -var detect = require('feature-detect-es6') var MODE_0666 = parseInt('0666', 8) var MODE_0755 = parseInt('0755', 8) @@ -76,7 +76,7 @@ if (!exit.exited) { * Install an around function; AOP. */ -function around(obj, method, fn) { +function around (obj, method, fn) { var old = obj[method] obj[method] = function () { @@ -90,7 +90,7 @@ function around(obj, method, fn) { * Install a before function; AOP. */ -function before(obj, method, fn) { +function before (obj, method, fn) { var old = obj[method] obj[method] = function () { @@ -103,7 +103,7 @@ function before(obj, method, fn) { * Prompt for confirmation on STDOUT/STDIN */ -function confirm(msg, callback) { +function confirm (msg, callback) { var rl = readline.createInterface({ input: process.stdin, output: process.stdout @@ -119,7 +119,7 @@ function confirm(msg, callback) { * Copy file from template directory. */ -function copyTemplate(from, to) { +function copyTemplate (from, to) { write(to, fs.readFileSync(path.join(TEMPLATE_DIR, from), 'utf-8')) } @@ -127,7 +127,7 @@ function copyTemplate(from, to) { * Copy multiple files from template directory. */ -function copyTemplateMulti(fromDir, toDir, nameGlob) { +function copyTemplateMulti (fromDir, toDir, nameGlob) { fs.readdirSync(path.join(TEMPLATE_DIR, fromDir)) .filter(minimatch.filter(nameGlob, { matchBase: true })) .forEach(function (name) { @@ -142,7 +142,7 @@ function copyTemplateMulti(fromDir, toDir, nameGlob) { * @param {string} dir */ -function createApplication(name, dir) { +function createApplication (name, dir) { console.log() // JavaScript @@ -355,7 +355,7 @@ function createApplication(name, dir) { * @param {String} pathName */ -function createAppName(pathName) { +function createAppName (pathName) { return path.basename(pathName) .replace(/[^A-Za-z0-9.()!~*'-]+/g, '-') .replace(/^[-_.]+|-+$/g, '') @@ -369,7 +369,7 @@ function createAppName(pathName) { * @param {Function} fn */ -function emptyDirectory(path, fn) { +function emptyDirectory (path, fn) { fs.readdir(path, function (err, files) { if (err && err.code !== 'ENOENT') throw err fn(!files || !files.length) @@ -380,11 +380,11 @@ function emptyDirectory(path, fn) { * Graceful exit for async STDIO */ -function exit(code) { +function exit (code) { // flush output for Node.js Windows pipe bug // https://github.com/joyent/node/issues/6247 is just one bug example // https://github.com/visionmedia/mocha/issues/333 has a good discussion - function done() { + function done () { if (!(draining--)) _exit(code) } @@ -406,7 +406,7 @@ function exit(code) { * Determine if launched from cmd.exe */ -function launchedFromCmd() { +function launchedFromCmd () { return process.platform === 'win32' && process.env._ === undefined } @@ -415,11 +415,11 @@ function launchedFromCmd() { * Load template file. */ -function loadTemplate(name) { +function loadTemplate (name) { var contents = fs.readFileSync(path.join(__dirname, '..', 'templates', (name + '.ejs')), 'utf-8') var locals = Object.create(null) - function render() { + function render () { return ejs.render(contents, locals) } @@ -433,7 +433,7 @@ function loadTemplate(name) { * Main program. */ -function main() { +function main () { // Path var destinationPath = program.args.shift() || '.' @@ -480,7 +480,7 @@ function main() { * @param {string} dir */ -function mkdir(base, dir) { +function mkdir (base, dir) { var loc = path.join(base, dir) console.log(' \x1b[36mcreate\x1b[0m : ' + loc + path.sep) @@ -494,7 +494,7 @@ function mkdir(base, dir) { * @param {String} newName */ -function renamedOption(originalName, newName) { +function renamedOption (originalName, newName) { return function (val) { warning(util.format("option `%s' has been renamed to `%s'", originalName, newName)) return val @@ -507,7 +507,7 @@ function renamedOption(originalName, newName) { * @param {String} message */ -function warning(message) { +function warning (message) { console.error() message.split('\n').forEach(function (line) { console.error(' warning: %s', line) @@ -522,7 +522,7 @@ function warning(message) { * @param {String} str */ -function write(path, str, mode) { +function write (path, str, mode) { fs.writeFileSync(path, str, { mode: mode || MODE_0666 }) console.log(' \x1b[36mcreate\x1b[0m : ' + path) } diff --git a/package.json b/package.json index 179fec32..4a4c0208 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "dependencies": { "commander": "2.11.0", "ejs": "2.5.7", + "feature-detect-es6": "1.3.1", "minimatch": "3.0.4", - "feature-detect-es6": "^1.3.1", "mkdirp": "0.5.1", "sorted-object": "2.0.1" }, diff --git a/test/cmd.js b/test/cmd.js index d0cbdce8..465a46bc 100644 --- a/test/cmd.js +++ b/test/cmd.js @@ -1014,7 +1014,7 @@ describe('express(1)', function () { }) }) }) - + describe('es2015', function () { var ctx = setupTestEnvironment(this.fullTitle()) @@ -1031,7 +1031,7 @@ describe('express(1)', function () { ['bin/www', 'routes/index.js', 'routes/users.js'].forEach(function (fileName) { var filePath = path.resolve(ctx.dir, fileName) var contents = fs.readFileSync(filePath, 'utf8') - + assert.equal(contents.indexOf('var'), -1) assert.notEqual(contents.indexOf('const'), -1) }) @@ -1092,7 +1092,7 @@ describe('express(1)', function () { ['bin/www', 'routes/index.js', 'routes/users.js'].forEach(function (fileName) { var filePath = path.resolve(ctx.dir, fileName) var contents = fs.readFileSync(filePath, 'utf8') - + assert.notEqual(contents.indexOf('var'), -1) assert.equal(contents.indexOf('const'), -1) })