diff --git a/.eslintrc.yml b/.eslintrc.yml index 5b09f907..21dacb60 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -3,3 +3,4 @@ extends: standard rules: no-param-reassign: error no-shadow: error + no-console: off diff --git a/.travis.yml b/.travis.yml index 13edaf41..8e9607f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,10 @@ language: node_js node_js: - - "0.10" - - "0.12" - - "1.8" - - "2.5" - - "3.3" - - "4.9" - - "5.12" - - "6.16" - - "7.10" - - "8.15" - - "9.11" + - '8.15' + - '9.11' + - 10 + - 11 + - 12 matrix: fast_finish: true sudo: false @@ -22,33 +16,6 @@ before_install: - | # Skip updating shrinkwrap / lock npm config set shrinkwrap false - # Setup Node.js version-specific dependencies - - | - # eslint for linting - # - remove on Node.js < 6 - if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then - node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ - grep -E '^eslint(-|$)' | \ - xargs npm rm --save-dev - fi - - | - # mocha for testing - # - use 3.x for Node.js < 4 - # - use 5.x for Node.js < 6 - if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 4 ]]; then - npm install --save-dev mocha@3.5.3 - elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then - npm install --save-dev mocha@5.2.0 - fi - - | - # supertest for http calls - # - use 2.0.0 for Node.js < 4 - # - use 3.4.2 for Node.js < 6 - if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 4 ]]; then - npm install --save-dev supertest@2.0.0 - elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then - npm install --save-dev supertest@3.4.2 - fi # Update Node.js modules - | # Prune and rebuild node_modules diff --git a/README.md b/README.md index ca18976e..50e8a494 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ This generator can also be further configured with the following command line fl --version output the version number -e, --ejs add ejs engine support + --es5 use ES5 syntax (defaults to ES2015 syntax) --pug add pug engine support --hbs add handlebars engine support -H, --hogan add hogan.js engine support diff --git a/appveyor.yml b/appveyor.yml index 6e5603a1..ebdc8d2e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,16 +1,10 @@ environment: matrix: - - nodejs_version: "0.10" - - nodejs_version: "0.12" - - nodejs_version: "1.8" - - nodejs_version: "2.5" - - nodejs_version: "3.3" - - nodejs_version: "4.9" - - nodejs_version: "5.12" - - nodejs_version: "6.16" - - nodejs_version: "7.10" - - nodejs_version: "8.15" - - nodejs_version: "9.11" + - nodejs_version: '8.15' + - nodejs_version: '9.11' + - nodejs_version: '10' + - nodejs_version: '11' + - nodejs_version: '12' cache: - node_modules install: @@ -28,25 +22,6 @@ install: cmd.exe /c "node -pe `"Object.keys(require('./package').devDependencies).join('\n')`"" | ` sls "^eslint(-|$)" | ` %{ npm rm --silent --save-dev $_ } - # Setup Node.js version-specific dependencies - - ps: | - # mocha for testing - # - use 3.x for Node.js < 4 - # - use 5.x for Node.js < 6 - if ([int]$env:nodejs_version.split(".")[0] -lt 4) { - npm install --silent --save-dev mocha@3.5.3 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 6) { - npm install --silent --save-dev mocha@5.2.0 - } - - ps: | - # supertest for http calls - # - use 2.0.0 for Node.js < 4 - # - use 3.4.2 for Node.js < 6 - if ([int]$env:nodejs_version.split(".")[0] -lt 4) { - npm install --silent --save-dev supertest@2.0.0 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 6) { - npm install --silent --save-dev supertest@3.4.2 - } # Update Node.js modules - ps: | # Prune & rebuild node_modules @@ -64,4 +39,4 @@ test_script: npm --version # Run test script - npm run test-ci -version: "{build}" +version: '{build}' diff --git a/bin/express-cli.js b/bin/express-cli.js index d0c80b7f..a06702c8 100755 --- a/bin/express-cli.js +++ b/bin/express-cli.js @@ -1,21 +1,21 @@ #!/usr/bin/env node -var ejs = require('ejs') -var fs = require('fs') -var minimatch = require('minimatch') -var mkdirp = require('mkdirp') -var path = require('path') -var program = require('commander') -var readline = require('readline') -var sortedObject = require('sorted-object') -var util = require('util') - -var MODE_0666 = parseInt('0666', 8) -var MODE_0755 = parseInt('0755', 8) -var TEMPLATE_DIR = path.join(__dirname, '..', 'templates') -var VERSION = require('../package').version - -var _exit = process.exit +const ejs = require('ejs') +const fs = require('fs') +const minimatch = require('minimatch') +const mkdirp = require('mkdirp') +const path = require('path') +const program = require('commander') +const readline = require('readline') +const sortedObject = require('sorted-object') +const util = require('util') + +const MODE_0666 = parseInt('0666', 8) +const MODE_0755 = parseInt('0755', 8) +const TEMPLATE_DIR = path.join(__dirname, '..', 'templates') +const VERSION = require('../package').version + +const _exit = process.exit // Re-assign process.exit because of commander // TODO: Switch to a different command framework @@ -48,13 +48,36 @@ program .name('express') .version(VERSION, ' --version') .usage('[options] [dir]') - .option('-e, --ejs', 'add ejs engine support', renamedOption('--ejs', '--view=ejs')) - .option(' --pug', 'add pug engine support', renamedOption('--pug', '--view=pug')) - .option(' --hbs', 'add handlebars engine support', renamedOption('--hbs', '--view=hbs')) - .option('-H, --hogan', 'add hogan.js engine support', renamedOption('--hogan', '--view=hogan')) - .option('-v, --view ', 'add view support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)') + .option( + '-e, --ejs', + 'add ejs engine support', + renamedOption('--ejs', '--view=ejs') + ) + .option(' --es5', 'use ES5 syntax (defaults to ES2015 syntax)') + .option( + ' --pug', + 'add pug engine support', + renamedOption('--pug', '--view=pug') + ) + .option( + ' --hbs', + 'add handlebars engine support', + renamedOption('--hbs', '--view=hbs') + ) + .option( + '-H, --hogan', + 'add hogan.js engine support', + renamedOption('--hogan', '--view=hogan') + ) + .option( + '-v, --view ', + 'add view support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)' + ) .option(' --no-view', 'use static html instead of view engine') - .option('-c, --css ', 'add stylesheet support (less|stylus|compass|sass) (defaults to plain css)') + .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') .parse(process.argv) @@ -64,25 +87,27 @@ if (!exit.exited) { } /** - * Install an around function; AOP. + * Install an around function AOP. */ function around (obj, method, fn) { - var old = obj[method] + const old = obj[method] obj[method] = function () { - var args = new Array(arguments.length) - for (var i = 0; i < args.length; i++) args[i] = arguments[i] + const args = new Array(arguments.length) + for (let i = 0; i < args.length; i++) { + args[i] = arguments[i] + } return fn.call(this, old, args) } } /** - * Install a before function; AOP. + * Install a before function AOP. */ function before (obj, method, fn) { - var old = obj[method] + const old = obj[method] obj[method] = function () { fn.call(this) @@ -95,12 +120,12 @@ function before (obj, method, fn) { */ function confirm (msg, callback) { - var rl = readline.createInterface({ + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) - rl.question(msg, function (input) { + rl.question(msg, (input) => { rl.close() callback(/^y|yes|ok|true$/i.test(input)) }) @@ -110,8 +135,8 @@ function confirm (msg, callback) { * Copy file from template directory. */ -function copyTemplate (from, to) { - write(to, fs.readFileSync(path.join(TEMPLATE_DIR, from), 'utf-8')) +function copyTemplate (original, to) { + write(to, fs.readFileSync(path.join(TEMPLATE_DIR, original), 'utf-8')) } /** @@ -121,7 +146,7 @@ function copyTemplate (from, to) { function copyTemplateMulti (fromDir, toDir, nameGlob) { fs.readdirSync(path.join(TEMPLATE_DIR, fromDir)) .filter(minimatch.filter(nameGlob, { matchBase: true })) - .forEach(function (name) { + .forEach((name) => { copyTemplate(path.join(fromDir, name), path.join(toDir, name)) }) } @@ -137,26 +162,34 @@ function createApplication (name, dir) { console.log() // Package - var pkg = { - name: name, + const pkg = { + name, version: '0.0.0', private: true, scripts: { start: 'node ./bin/www' }, dependencies: { - 'debug': '~2.6.9', - 'express': '~4.16.1' + debug: '~2.6.9', + express: '~4.16.1' } } // JavaScript - var app = loadTemplate('js/app.js') - var www = loadTemplate('js/www') + const app = loadTemplate('js/app.js') + const www = loadTemplate('js/www') + const index = loadTemplate('js/routes/index') + const users = loadTemplate('js/routes/users') // App name www.locals.name = name + // ES2015 Syntax + app.locals.es5 = program.es5 + www.locals.es5 = program.es5 + index.locals.es5 = program.es5 + users.locals.es5 = program.es5 + // App modules app.locals.localModules = Object.create(null) app.locals.modules = Object.create(null) @@ -205,10 +238,6 @@ function createApplication (name, dir) { break } - // copy route templates - mkdir(dir, 'routes') - copyTemplateMulti('js/routes', dir + '/routes', '*.js') - if (program.view) { // Copy view templates mkdir(dir, 'views') @@ -258,7 +287,9 @@ function createApplication (name, dir) { break case 'sass': app.locals.modules.sassMiddleware = 'node-sass-middleware' - app.locals.uses.push("sassMiddleware({\n src: path.join(__dirname, 'public'),\n dest: path.join(__dirname, 'public'),\n indentedSyntax: true, // true = .sass and false = .scss\n sourceMap: true\n})") + app.locals.uses.push( + "sassMiddleware({\n src: path.join(__dirname, 'public'),\n dest: path.join(__dirname, 'public'),\n indentedSyntax: true, // true = .sass and false = .scss\n sourceMap: true\n})" + ) pkg.dependencies['node-sass-middleware'] = '0.11.0' break case 'stylus': @@ -334,25 +365,29 @@ function createApplication (name, dir) { write(path.join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n') mkdir(dir, 'bin') write(path.join(dir, 'bin/www'), www.render(), MODE_0755) + mkdir(dir, 'routes') + write(path.join(dir, 'routes/index.js'), index.render()) + write(path.join(dir, 'routes/users.js'), users.render()) - var prompt = launchedFromCmd() ? '>' : '$' + const prompt = launchedFromCmd() ? '>' : '$' if (dir !== '.') { console.log() - console.log(' change directory:') - console.log(' %s cd %s', prompt, dir) + console.log(` + change directory: + ${prompt} cd ${dir}`) } - console.log() - console.log(' install dependencies:') - console.log(' %s npm install', prompt) - console.log() - console.log(' run the app:') + console.log(` + install dependencies: + ${prompt} npm install + + run the app:`) if (launchedFromCmd()) { - console.log(' %s SET DEBUG=%s:* & npm start', prompt, name) + console.log(` ${prompt} SET DEBUG=${name}:* & npm start'`) } else { - console.log(' %s DEBUG=%s:* npm start', prompt, name) + console.log(` ${prompt} DEBUG=${name}:* npm start`) } console.log() @@ -365,7 +400,8 @@ function createApplication (name, dir) { */ function createAppName (pathName) { - return path.basename(pathName) + return path + .basename(pathName) .replace(/[^A-Za-z0-9.-]+/g, '-') .replace(/^[-_.]+|-+$/g, '') .toLowerCase() @@ -379,8 +415,10 @@ function createAppName (pathName) { */ function emptyDirectory (dir, fn) { - fs.readdir(dir, function (err, files) { - if (err && err.code !== 'ENOENT') throw err + fs.readdir(dir, (err, files) => { + if (err && err.code !== 'ENOENT') { + throw err + } fn(!files || !files.length) }) } @@ -394,15 +432,17 @@ function exit (code) { // 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 () { - if (!(draining--)) _exit(code) + if (!draining--) { + _exit(code) + } } - var draining = 0 - var streams = [process.stdout, process.stderr] + let draining = 0 + const streams = [process.stdout, process.stderr] exit.exited = true - streams.forEach(function (stream) { + streams.forEach((stream) => { // submit empty write request and wait for completion draining += 1 stream.write('', done) @@ -416,8 +456,7 @@ function exit (code) { */ function launchedFromCmd () { - return process.platform === 'win32' && - process.env._ === undefined + return process.platform === 'win32' && process.env._ === undefined } /** @@ -425,8 +464,11 @@ function launchedFromCmd () { */ function loadTemplate (name) { - var contents = fs.readFileSync(path.join(__dirname, '..', 'templates', (name + '.ejs')), 'utf-8') - var locals = Object.create(null) + const contents = fs.readFileSync( + path.join(__dirname, '..', 'templates', name + '.ejs'), + 'utf-8' + ) + const locals = Object.create(null) function render () { return ejs.render(contents, locals, { @@ -435,8 +477,8 @@ function loadTemplate (name) { } return { - locals: locals, - render: render + locals, + render } } @@ -446,32 +488,39 @@ function loadTemplate (name) { function main () { // Path - var destinationPath = program.args.shift() || '.' + const destinationPath = program.args.shift() || '.' // App name - var appName = createAppName(path.resolve(destinationPath)) || 'hello-world' + const appName = createAppName(path.resolve(destinationPath)) || 'hello-world' // View engine if (program.view === true) { - if (program.ejs) program.view = 'ejs' - if (program.hbs) program.view = 'hbs' - if (program.hogan) program.view = 'hjs' - if (program.pug) program.view = 'pug' + if (program.ejs) { + program.view = 'ejs' + } else if (program.hbs) { + program.view = 'hbs' + } else if (program.hogan) { + program.view = 'hjs' + } else if (program.pug) { + program.view = 'pug' + } } // Default view engine if (program.view === true) { - warning('the default view engine will not be jade in future releases\n' + - "use `--view=jade' or `--help' for additional options") + warning( + 'the default view engine will not be jade in future releases\n' + + "use `--view=jade' or `--help' for additional options" + ) program.view = 'jade' } // Generate application - emptyDirectory(destinationPath, function (empty) { + emptyDirectory(destinationPath, (empty) => { if (empty || program.force) { createApplication(appName, destinationPath) } else { - confirm('destination is not empty, continue? [y/N] ', function (ok) { + confirm('destination is not empty, continue? [y/N] ', (ok) => { if (ok) { process.stdin.destroy() createApplication(appName, destinationPath) @@ -492,9 +541,9 @@ function main () { */ function mkdir (base, dir) { - var loc = path.join(base, dir) + const loc = path.join(base, dir) - console.log(' \x1b[36mcreate\x1b[0m : ' + loc + path.sep) + console.log(` \x1b[36mcreate\x1b[0m : ${loc}${path.sep}`) mkdirp.sync(loc, MODE_0755) } @@ -506,8 +555,14 @@ function mkdir (base, dir) { */ function renamedOption (originalName, newName) { - return function (val) { - warning(util.format("option `%s' has been renamed to `%s'", originalName, newName)) + return (val) => { + warning( + util.format( + "option `%s' has been renamed to `%s'", + originalName, + newName + ) + ) return val } } @@ -520,8 +575,8 @@ function renamedOption (originalName, newName) { function warning (message) { console.error() - message.split('\n').forEach(function (line) { - console.error(' warning: %s', line) + message.split('\n').forEach((line) => { + console.error(` warning: ${line}`) }) console.error() } @@ -535,5 +590,5 @@ function warning (message) { function write (file, str, mode) { fs.writeFileSync(file, str, { mode: mode || MODE_0666 }) - console.log(' \x1b[36mcreate\x1b[0m : ' + file) + console.log(` \x1b[36mcreate\x1b[0m : ${file}`) } diff --git a/package.json b/package.json index f9bfe584..b3b0ed13 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express-generator", "description": "Express' application generator", - "version": "4.16.1", + "version": "5.0.0", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", @@ -51,7 +51,7 @@ "validate-npm-package-name": "3.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 8.0.0" }, "files": [ "LICENSE", diff --git a/templates/js/app.js.ejs b/templates/js/app.js.ejs index 2ef75aec..3e35b94b 100644 --- a/templates/js/app.js.ejs +++ b/templates/js/app.js.ejs @@ -1,17 +1,37 @@ -<% if (view) { -%> +<% if (view && es5) { -%> var createError = require('http-errors'); +<% } else if (view) { -%> +const createError = require('http-errors'); <% } -%> + +<% if (es5) { -%> var express = require('express'); var path = require('path'); +<% } else { -%> +const express = require('express'); +const path = require('path'); +<% } -%> <% Object.keys(modules).sort().forEach(function (variable) { -%> +<% if (es5) { -%> var <%- variable %> = require('<%- modules[variable] %>'); +<% } else { -%> +const <%- variable %> = require('<%- modules[variable] %>'); +<% } -%> <% }); -%> <% Object.keys(localModules).sort().forEach(function (variable) { -%> +<% if (es5) { -%> var <%- variable %> = require('<%- localModules[variable] %>'); +<% } else { -%> +const <%- variable %> = require('<%- localModules[variable] %>'); +<% } -%> <% }); -%> +<% if (es5) { -%> var app = express(); +<% } else { -%> +const app = express(); +<% } -%> <% if (view) { -%> // view engine setup @@ -30,14 +50,31 @@ app.use(<%- use %>); app.use(<%= mount.path %>, <%- mount.code %>); <% }); -%> -<% if (view) { -%> +<% if (view && es5) { -%> +// catch 404 and forward to error handler +app.use(function (req, res, next) { + next(createError(404)); +}); + +// error handler +app.use(function (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'); +}); + +<% } else if (view) { -%> // catch 404 and forward to error handler -app.use(function(req, res, next) { +app.use((req, res, next) => { next(createError(404)); }); // error handler -app.use(function(err, req, res, next) { +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 : {}; diff --git a/templates/js/routes/index.ejs b/templates/js/routes/index.ejs new file mode 100644 index 00000000..fab2a85e --- /dev/null +++ b/templates/js/routes/index.ejs @@ -0,0 +1,18 @@ +<% if (es5) { -%> +var express = require('express'); +var router = express.Router(); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('index', { title: 'Express' }); +}); +<% } else { -%> +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/routes/index.js b/templates/js/routes/index.js deleted file mode 100644 index ecca96a5..00000000 --- a/templates/js/routes/index.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', { title: 'Express' }); -}); - -module.exports = router; diff --git a/templates/js/routes/users.ejs b/templates/js/routes/users.ejs new file mode 100644 index 00000000..b1916e77 --- /dev/null +++ b/templates/js/routes/users.ejs @@ -0,0 +1,19 @@ +<% if (es5) { -%> +var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +router.get('/', function(req, res, next) { + res.send('respond with a resource'); +}); +<% } else { -%> +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/routes/users.js b/templates/js/routes/users.js deleted file mode 100644 index 623e4302..00000000 --- a/templates/js/routes/users.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET users listing. */ -router.get('/', function(req, res, next) { - res.send('respond with a resource'); -}); - -module.exports = router; diff --git a/templates/js/www.ejs b/templates/js/www.ejs index 690fc488..649d8e57 100644 --- a/templates/js/www.ejs +++ b/templates/js/www.ejs @@ -4,10 +4,10 @@ * Module dependencies. */ +<% if (es5) { -%> var app = require('../app'); var debug = require('debug')('<%- name %>:server'); var http = require('http'); - /** * Get port from environment and store in Express. */ @@ -88,3 +88,89 @@ function onListening() { : 'port ' + addr.port; debug('Listening on ' + bind); } + +<% } else { -%> + 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); + } +<% } -%> \ No newline at end of file diff --git a/test/cmd.js b/test/cmd.js index c0711a73..6553e924 100644 --- a/test/cmd.js +++ b/test/cmd.js @@ -1,21 +1,23 @@ - -var assert = require('assert') -var AppRunner = require('./support/app-runner') -var exec = require('child_process').exec -var fs = require('fs') -var mkdirp = require('mkdirp') -var path = require('path') -var request = require('supertest') -var rimraf = require('rimraf') -var spawn = require('child_process').spawn -var utils = require('./support/utils') -var validateNpmName = require('validate-npm-package-name') - -var APP_START_STOP_TIMEOUT = 10000 -var PKG_PATH = path.resolve(__dirname, '..', 'package.json') -var BIN_PATH = path.resolve(path.dirname(PKG_PATH), require(PKG_PATH).bin.express) -var NPM_INSTALL_TIMEOUT = 300000 // 5 minutes -var TEMP_DIR = utils.tmpDir() +const assert = require('assert') +const AppRunner = require('./support/app-runner') +const exec = require('child_process').exec +const fs = require('fs') +const mkdirp = require('mkdirp') +const path = require('path') +const request = require('supertest') +const rimraf = require('rimraf') +const spawn = require('child_process').spawn +const utils = require('./support/utils') +const validateNpmName = require('validate-npm-package-name') + +const APP_START_STOP_TIMEOUT = 10000 +const PKG_PATH = path.resolve(__dirname, '..', 'package.json') +const BIN_PATH = path.resolve( + path.dirname(PKG_PATH), + require(PKG_PATH).bin.express +) +const NPM_INSTALL_TIMEOUT = 300000 // 5 minutes +const TEMP_DIR = utils.tmpDir() describe('express(1)', function () { after(function (done) { @@ -24,11 +26,13 @@ describe('express(1)', function () { }) describe('(no args)', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app', function (done) { runRaw(ctx.dir, [], function (err, code, stdout, stderr) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) ctx.stderr = stderr ctx.stdout = stdout @@ -38,18 +42,17 @@ describe('express(1)', function () { }) it('should print jade view warning', function () { - assert.strictEqual(ctx.stderr, "\n warning: the default view engine will not be jade in future releases\n warning: use `--view=jade' or `--help' for additional options\n\n") + assert.strictEqual( + ctx.stderr, + "\n warning: the default view engine will not be jade in future releases\n warning: use `--view=jade' or `--help' for additional options\n\n" + ) }) it('should provide debug instructions', function () { assert.ok(/DEBUG=express-1-no-args:\* (?:& )?npm start/.test(ctx.stdout)) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have jade templates', function () { assert.notStrictEqual(ctx.files.indexOf('views/error.jade'), -1) @@ -58,24 +61,27 @@ describe('express(1)', function () { }) it('should have a package.json file', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - assert.strictEqual(contents, '{\n' + - ' "name": "express-1-no-args",\n' + - ' "version": "0.0.0",\n' + - ' "private": true,\n' + - ' "scripts": {\n' + - ' "start": "node ./bin/www"\n' + - ' },\n' + - ' "dependencies": {\n' + - ' "cookie-parser": "~1.4.4",\n' + - ' "debug": "~2.6.9",\n' + - ' "express": "~4.16.1",\n' + - ' "http-errors": "~1.6.3",\n' + - ' "jade": "~1.11.0",\n' + - ' "morgan": "~1.9.1"\n' + - ' }\n' + - '}\n') + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + assert.strictEqual( + contents, + '{\n' + + ' "name": "express-1-no-args",\n' + + ' "version": "0.0.0",\n' + + ' "private": true,\n' + + ' "scripts": {\n' + + ' "start": "node ./bin/www"\n' + + ' },\n' + + ' "dependencies": {\n' + + ' "cookie-parser": "~1.4.4",\n' + + ' "debug": "~2.6.9",\n' + + ' "express": "~4.16.1",\n' + + ' "http-errors": "~1.6.3",\n' + + ' "jade": "~1.11.0",\n' + + ' "morgan": "~1.9.1"\n' + + ' }\n' + + '}\n' + ) }) it('should have installable dependencies', function (done) { @@ -84,8 +90,8 @@ describe('express(1)', function () { }) it('should export an express app from app.js', function () { - var file = path.resolve(ctx.dir, 'app.js') - var app = require(file) + const file = path.resolve(ctx.dir, 'app.js') + const app = require(file) assert.strictEqual(typeof app, 'function') assert.strictEqual(typeof app.handle, 'function') }) @@ -119,71 +125,220 @@ describe('express(1)', function () { }) describe('when directory contains spaces', function () { - var ctx0 = setupTestEnvironment('foo bar (BAZ!)') + const ctx0 = setupTestEnvironment('foo bar (BAZ!)') it('should create basic app', function (done) { run(ctx0.dir, [], function (err, output) { - if (err) return done(err) - assert.strictEqual(utils.parseCreatedFiles(output, ctx0.dir).length, 16) + if (err) { + return done(err) + } + assert.strictEqual( + utils.parseCreatedFiles(output, ctx0.dir).length, + 16 + ) done() }) }) it('should have a valid npm package name', function () { - var file = path.resolve(ctx0.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var name = JSON.parse(contents).name - assert.ok(validateNpmName(name).validForNewPackages, 'package name "' + name + '" is valid') + const file = path.resolve(ctx0.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const name = JSON.parse(contents).name + assert.ok( + validateNpmName(name).validForNewPackages, + 'package name "' + name + '" is valid' + ) assert.strictEqual(name, 'foo-bar-baz') }) }) describe('when directory is not a valid name', function () { - var ctx1 = setupTestEnvironment('_') + const ctx1 = setupTestEnvironment('_') it('should create basic app', function (done) { run(ctx1.dir, [], function (err, output) { - if (err) return done(err) - assert.strictEqual(utils.parseCreatedFiles(output, ctx1.dir).length, 16) + if (err) { + return done(err) + } + assert.strictEqual( + utils.parseCreatedFiles(output, ctx1.dir).length, + 16 + ) done() }) }) it('should default to name "hello-world"', function () { - var file = path.resolve(ctx1.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var name = JSON.parse(contents).name + const file = path.resolve(ctx1.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const name = JSON.parse(contents).name assert.ok(validateNpmName(name).validForNewPackages) assert.strictEqual(name, 'hello-world') }) }) }) - describe('(unknown args)', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + describe('--es5', function () { + const ctx = setupTestEnvironment(this.fullTitle()) - it('should exit with code 1', function (done) { - runRaw(ctx.dir, ['--foo'], function (err, code, stdout, stderr) { - if (err) return done(err) - assert.strictEqual(code, 1) + it('should create basic app', function (done) { + runRaw(ctx.dir, ['--es5'], function (err, code, stdout, stderr) { + if (err) { + return done(err) + } + ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) + ctx.stderr = stderr + ctx.stdout = stdout + assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should print usage', function (done) { - runRaw(ctx.dir, ['--foo'], function (err, code, stdout, stderr) { - if (err) return done(err) - assert.ok(/Usage: express /.test(stdout)) - assert.ok(/--help/.test(stdout)) - assert.ok(/--version/.test(stdout)) - assert.ok(/error: unknown option/.test(stderr)) - done() + it('should print jade view warning', function () { + assert.strictEqual( + ctx.stderr, + "\n warning: the default view engine will not be jade in future releases\n warning: use `--view=jade' or `--help' for additional options\n\n" + ) + }) + + checkBasicFiles(ctx) + + it('should have jade templates', function () { + assert.notStrictEqual(ctx.files.indexOf('views/error.jade'), -1) + assert.notStrictEqual(ctx.files.indexOf('views/index.jade'), -1) + assert.notStrictEqual(ctx.files.indexOf('views/layout.jade'), -1) + }) + + it('should have a package.json file', function () { + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + assert.strictEqual( + contents, + '{\n' + + ' "name": "express-1---es5",\n' + + ' "version": "0.0.0",\n' + + ' "private": true,\n' + + ' "scripts": {\n' + + ' "start": "node ./bin/www"\n' + + ' },\n' + + ' "dependencies": {\n' + + ' "cookie-parser": "~1.4.4",\n' + + ' "debug": "~2.6.9",\n' + + ' "express": "~4.16.1",\n' + + ' "http-errors": "~1.6.3",\n' + + ' "jade": "~1.11.0",\n' + + ' "morgan": "~1.9.1"\n' + + ' }\n' + + '}\n' + ) + }) + + it('should have installable dependencies', function (done) { + this.timeout(NPM_INSTALL_TIMEOUT) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + const file = path.resolve(ctx.dir, 'app.js') + const app = require(file) + assert.strictEqual(typeof app, 'function') + assert.strictEqual(typeof app.handle, 'function') + }) + + describe('npm start', function () { + before('start app', function () { + this.app = new AppRunner(ctx.dir) + }) + + after('stop app', function (done) { + this.timeout(APP_START_STOP_TIMEOUT) + this.app.stop(done) + }) + + it('should start app', function (done) { + this.timeout(APP_START_STOP_TIMEOUT) + 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('when directory contains spaces', function () { + const ctx0 = setupTestEnvironment('foo bar (BAZ!)') + + it('should create basic app', function (done) { + run(ctx0.dir, [], function (err, output) { + if (err) { + return done(err) + } + assert.strictEqual( + utils.parseCreatedFiles(output, ctx0.dir).length, + 16 + ) + done() + }) + }) + + it('should have a valid npm package name', function () { + const file = path.resolve(ctx0.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const name = JSON.parse(contents).name + assert.ok( + validateNpmName(name).validForNewPackages, + 'package name "' + name + '" is valid' + ) + assert.strictEqual(name, 'foo-bar-baz') + }) + }) + + describe('when directory is not a valid name', function () { + const ctx1 = setupTestEnvironment('_') + + it('should create basic app', function (done) { + run(ctx1.dir, [], function (err, output) { + if (err) { + return done(err) + } + assert.strictEqual( + utils.parseCreatedFiles(output, ctx1.dir).length, + 16 + ) + done() + }) + }) + + it('should default to name "hello-world"', function () { + const file = path.resolve(ctx1.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const name = JSON.parse(contents).name + assert.ok(validateNpmName(name).validForNewPackages) + assert.strictEqual(name, 'hello-world') }) }) + }) + + describe('(unknown args)', function () { + const ctx = setupTestEnvironment(this.fullTitle()) + + checkAppExits(ctx, '--foo') + + checkMessage(ctx, '--foo', { runRaw: true, isUnknownArg: true }) it('should print unknown option', function (done) { runRaw(ctx.dir, ['--foo'], function (err, code, stdout, stderr) { - if (err) return done(err) + if (err) { + return done(err) + } assert.ok(/error: unknown option/.test(stderr)) done() }) @@ -191,11 +346,13 @@ describe('express(1)', function () { }) describe('<dir>', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app in directory', function (done) { runRaw(ctx.dir, ['foo'], function (err, code, stdout, stderr) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) ctx.stderr = stderr ctx.stdout = stdout @@ -231,29 +388,17 @@ describe('express(1)', function () { describe('--css <engine>', function () { describe('(no engine)', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) - it('should exit with code 1', function (done) { - runRaw(ctx.dir, ['--css'], function (err, code, stdout, stderr) { - if (err) return done(err) - assert.strictEqual(code, 1) - done() - }) - }) + checkAppExits(ctx, '--css') - it('should print usage', function (done) { - runRaw(ctx.dir, ['--css'], function (err, code, stdout) { - if (err) return done(err) - assert.ok(/Usage: express /.test(stdout)) - assert.ok(/--help/.test(stdout)) - assert.ok(/--version/.test(stdout)) - done() - }) - }) + checkMessage(ctx, '--css', { runRaw: true }) it('should print argument missing', function (done) { runRaw(ctx.dir, ['--css'], function (err, code, stdout, stderr) { - if (err) return done(err) + if (err) { + return done(err) + } assert.ok(/error: option .* argument missing/.test(stderr)) done() }) @@ -261,25 +406,27 @@ describe('express(1)', function () { }) describe('less', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with less files', function (done) { run(ctx.dir, ['--css', 'less'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16, 'should have 16 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have less files', function () { - assert.notStrictEqual(ctx.files.indexOf('public/stylesheets/style.less'), -1, 'should have style.less file') + assert.notStrictEqual( + ctx.files.indexOf('public/stylesheets/style.less'), + -1, + 'should have style.less file' + ) }) it('should have installable dependencies', function (done) { @@ -317,25 +464,27 @@ describe('express(1)', function () { }) describe('sass', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with sass files', function (done) { run(ctx.dir, ['--css', 'sass'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16, 'should have 16 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have sass files', function () { - assert.notStrictEqual(ctx.files.indexOf('public/stylesheets/style.sass'), -1, 'should have style.sass file') + assert.notStrictEqual( + ctx.files.indexOf('public/stylesheets/style.sass'), + -1, + 'should have style.sass file' + ) }) it('should have installable dependencies', function (done) { @@ -373,25 +522,27 @@ describe('express(1)', function () { }) describe('stylus', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with stylus files', function (done) { run(ctx.dir, ['--css', 'stylus'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16, 'should have 16 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have stylus files', function () { - assert.notStrictEqual(ctx.files.indexOf('public/stylesheets/style.styl'), -1, 'should have style.styl file') + assert.notStrictEqual( + ctx.files.indexOf('public/stylesheets/style.styl'), + -1, + 'should have style.styl file' + ) }) it('should have installable dependencies', function (done) { @@ -430,49 +581,57 @@ describe('express(1)', function () { }) describe('--ejs', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with ejs templates', function (done) { run(ctx.dir, ['--ejs'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 15, 'should have 15 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have ejs templates', function () { - assert.notStrictEqual(ctx.files.indexOf('views/error.ejs'), -1, 'should have views/error.ejs file') - assert.notStrictEqual(ctx.files.indexOf('views/index.ejs'), -1, 'should have views/index.ejs file') + assert.notStrictEqual( + ctx.files.indexOf('views/error.ejs'), + -1, + 'should have views/error.ejs file' + ) + assert.notStrictEqual( + ctx.files.indexOf('views/index.ejs'), + -1, + 'should have views/index.ejs file' + ) }) }) describe('--git', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with git files', function (done) { run(ctx.dir, ['--git'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 17, 'should have 17 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have .gitignore', function () { - assert.notStrictEqual(ctx.files.indexOf('.gitignore'), -1, 'should have .gitignore file') + assert.notStrictEqual( + ctx.files.indexOf('.gitignore'), + -1, + 'should have .gitignore file' + ) }) it('should have jade templates', function () { @@ -483,43 +642,31 @@ describe('express(1)', function () { }) describe('-h', function () { - var ctx = setupTestEnvironment(this.fullTitle()) - - it('should print usage', function (done) { - run(ctx.dir, ['-h'], function (err, stdout) { - if (err) return done(err) - var files = utils.parseCreatedFiles(stdout, ctx.dir) - assert.strictEqual(files.length, 0) - assert.ok(/Usage: express /.test(stdout)) - assert.ok(/--help/.test(stdout)) - assert.ok(/--version/.test(stdout)) - done() - }) - }) + const ctx = setupTestEnvironment(this.fullTitle()) + + checkMessage(ctx, '-h', { numFiles: 0 }) }) describe('--hbs', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with hbs templates', function (done) { run(ctx.dir, ['--hbs'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have hbs in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.hbs === 'string') }) @@ -531,43 +678,31 @@ describe('express(1)', function () { }) describe('--help', function () { - var ctx = setupTestEnvironment(this.fullTitle()) - - it('should print usage', function (done) { - run(ctx.dir, ['--help'], function (err, stdout) { - if (err) return done(err) - var files = utils.parseCreatedFiles(stdout, ctx.dir) - assert.strictEqual(files.length, 0) - assert.ok(/Usage: express /.test(stdout)) - assert.ok(/--help/.test(stdout)) - assert.ok(/--version/.test(stdout)) - done() - }) - }) + const ctx = setupTestEnvironment(this.fullTitle()) + + checkMessage(ctx, '--help', { numFiles: 0 }) }) describe('--hogan', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with hogan templates', function (done) { run(ctx.dir, ['--hogan'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 15) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have hjs in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.hjs === 'string') }) @@ -578,22 +713,20 @@ describe('express(1)', function () { }) describe('--no-view', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app without view engine', function (done) { run(ctx.dir, ['--no-view'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 13) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should not have views directory', function () { assert.strictEqual(ctx.files.indexOf('views'), -1) @@ -634,27 +767,25 @@ describe('express(1)', function () { }) describe('--pug', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with pug templates', function (done) { run(ctx.dir, ['--pug'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have pug in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.pug === 'string') }) @@ -667,29 +798,17 @@ describe('express(1)', function () { describe('--view <engine>', function () { describe('(no engine)', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) - it('should exit with code 1', function (done) { - runRaw(ctx.dir, ['--view'], function (err, code, stdout, stderr) { - if (err) return done(err) - assert.strictEqual(code, 1) - done() - }) - }) + checkAppExits(ctx, '--view') - it('should print usage', function (done) { - runRaw(ctx.dir, ['--view'], function (err, code, stdout) { - if (err) return done(err) - assert.ok(/Usage: express /.test(stdout)) - assert.ok(/--help/.test(stdout)) - assert.ok(/--version/.test(stdout)) - done() - }) - }) + checkMessage(ctx, '--view', { runRaw: true }) it('should print argument missing', function (done) { runRaw(ctx.dir, ['--view'], function (err, code, stdout, stderr) { - if (err) return done(err) + if (err) { + return done(err) + } assert.ok(/error: option .* argument missing/.test(stderr)) done() }) @@ -697,26 +816,32 @@ describe('express(1)', function () { }) describe('dust', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with dust templates', function (done) { run(ctx.dir, ['--view', 'dust'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 15, 'should have 15 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have dust templates', function () { - assert.notStrictEqual(ctx.files.indexOf('views/error.dust'), -1, 'should have views/error.dust file') - assert.notStrictEqual(ctx.files.indexOf('views/index.dust'), -1, 'should have views/index.dust file') + assert.notStrictEqual( + ctx.files.indexOf('views/error.dust'), + -1, + 'should have views/error.dust file' + ) + assert.notStrictEqual( + ctx.files.indexOf('views/index.dust'), + -1, + 'should have views/index.dust file' + ) }) it('should have installable dependencies', function (done) { @@ -724,56 +849,36 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) describe('ejs', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with ejs templates', function (done) { run(ctx.dir, ['--view', 'ejs'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 15, 'should have 15 files') done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') - }) + checkBasicFiles(ctx) it('should have ejs templates', function () { - assert.notStrictEqual(ctx.files.indexOf('views/error.ejs'), -1, 'should have views/error.ejs file') - assert.notStrictEqual(ctx.files.indexOf('views/index.ejs'), -1, 'should have views/index.ejs file') + assert.notStrictEqual( + ctx.files.indexOf('views/error.ejs'), + -1, + 'should have views/error.ejs file' + ) + assert.notStrictEqual( + ctx.files.indexOf('views/index.ejs'), + -1, + 'should have views/index.ejs file' + ) }) it('should have installable dependencies', function (done) { @@ -781,57 +886,29 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) describe('hbs', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with hbs templates', function (done) { run(ctx.dir, ['--view', 'hbs'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have hbs in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.hbs === 'string') }) @@ -846,57 +923,29 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) describe('hjs', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with hogan templates', function (done) { run(ctx.dir, ['--view', 'hjs'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 15) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have hjs in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.hjs === 'string') }) @@ -910,57 +959,29 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) describe('pug', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with pug templates', function (done) { run(ctx.dir, ['--view', 'pug'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have pug in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.pug === 'string') }) @@ -975,57 +996,29 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) describe('twig', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with twig templates', function (done) { run(ctx.dir, ['--view', 'twig'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have twig in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.twig === 'string') }) @@ -1040,57 +1033,29 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) describe('vash', function () { - var ctx = setupTestEnvironment(this.fullTitle()) + const ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with vash templates', function (done) { run(ctx.dir, ['--view', 'vash'], function (err, stdout) { - if (err) return done(err) + if (err) { + return done(err) + } ctx.files = utils.parseCreatedFiles(stdout, ctx.dir) assert.strictEqual(ctx.files.length, 16) done() }) }) - it('should have basic files', function () { - assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) - assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) - assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) - }) + checkBasicFiles(ctx) it('should have vash in package dependencies', function () { - var file = path.resolve(ctx.dir, 'package.json') - var contents = fs.readFileSync(file, 'utf8') - var dependencies = JSON.parse(contents).dependencies + const file = path.resolve(ctx.dir, 'package.json') + const contents = fs.readFileSync(file, 'utf8') + const dependencies = JSON.parse(contents).dependencies assert.ok(typeof dependencies.vash === 'string') }) @@ -1105,39 +1070,13 @@ describe('express(1)', function () { npmInstall(ctx.dir, done) }) - describe('npm start', function () { - before('start app', function () { - this.app = new AppRunner(ctx.dir) - }) - - after('stop app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - this.app.stop(done) - }) - - it('should start app', function (done) { - this.timeout(APP_START_STOP_TIMEOUT) - 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) - }) - }) + checkAppStart(ctx) }) }) }) function npmInstall (dir, callback) { - var env = utils.childEnvironment() + const env = utils.childEnvironment() exec('npm install', { cwd: dir, env: env }, function (err, stderr) { if (err) { @@ -1170,12 +1109,12 @@ function run (dir, args, callback) { } function runRaw (dir, args, callback) { - var argv = [BIN_PATH].concat(args) - var binp = process.argv[0] - var stderr = '' - var stdout = '' + const argv = [BIN_PATH].concat(args) + const binp = process.argv[0] + let stderr = '' + let stdout = '' - var child = spawn(binp, argv, { + const child = spawn(binp, argv, { cwd: dir }) @@ -1197,7 +1136,7 @@ function runRaw (dir, args, callback) { } function setupTestEnvironment (name) { - var ctx = {} + const ctx = {} before('create environment', function (done) { ctx.dir = path.join(TEMP_DIR, name.replace(/[<>]/g, '')) @@ -1211,3 +1150,89 @@ function setupTestEnvironment (name) { return ctx } + +function checkAppExits (ctx, arg) { + it('should exit with code 1', function (done) { + // eslint-disable-next-line no-unused-vars + runRaw(ctx.dir, [arg], function (err, code, stdout, stderr) { + if (err) { + return done(err) + } + assert.strictEqual(code, 1) + done() + }) + }) +} + +function checkMessage (ctx, arg, options = {}) { + it('should print usage', function (done) { + if (options.runRaw) { + runRaw(ctx.dir, [arg], function (err, code, stdout, stderr) { + if (err) { + return done(err) + } + checkMessageContent(ctx, { stdout, stderr }, options) + done() + }) + } else { + run(ctx.dir, [arg], function (err, stdout) { + if (err) { + return done(err) + } + checkMessageContent(ctx, { stdout }, options) + done() + }) + } + }) +} + +function checkMessageContent (ctx, { stdout, stderr }, { isUnknownArg = false, expectedFileNum = null }) { + if (expectedFileNum) { + const files = utils.parseCreatedFiles(stdout, ctx.dir) + assert.strictEqual(files.length, expectedFileNum) + } + assert.ok(/Usage: express /.test(stdout)) + assert.ok(/--help/.test(stdout)) + assert.ok(/--version/.test(stdout)) + if (isUnknownArg) { + assert.ok(/error: unknown option/.test(stderr)) + } +} + +function checkBasicFiles (ctx) { + it('should have basic files', function () { + assert.notStrictEqual(ctx.files.indexOf('bin/www'), -1) + assert.notStrictEqual(ctx.files.indexOf('app.js'), -1) + assert.notStrictEqual(ctx.files.indexOf('package.json'), -1) + }) +} + +function checkAppStart (ctx) { + describe('npm start', function () { + before('start app', function () { + this.app = new AppRunner(ctx.dir) + }) + + after('stop app', function (done) { + this.timeout(APP_START_STOP_TIMEOUT) + this.app.stop(done) + }) + + it('should start app', function (done) { + this.timeout(APP_START_STOP_TIMEOUT) + 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) + }) + }) +} diff --git a/test/support/app-runner.js b/test/support/app-runner.js index 5cc7ae20..516be23c 100644 --- a/test/support/app-runner.js +++ b/test/support/app-runner.js @@ -1,84 +1,87 @@ 'use strict' -var exec = require('child_process').exec -var kill = require('tree-kill') -var net = require('net') -var utils = require('./utils') - -module.exports = AppRunner - -function AppRunner (dir) { - this.child = null - this.dir = dir - this.host = '127.0.0.1' - this.port = 3000 -} - -AppRunner.prototype.address = function address () { - return { port: this.port } -} - -AppRunner.prototype.start = function start (callback) { - var app = this - var done = false - var env = utils.childEnvironment() - - env.PORT = String(app.port) - - this.child = exec('npm start', { - cwd: this.dir, - env: env - }) - - this.child.stderr.pipe(process.stderr, { end: false }) +const exec = require('child_process').exec +const kill = require('tree-kill') +const net = require('net') +const utils = require('./utils') + +class AppRunner { + constructor (dir) { + this.child = null + this.dir = dir + this.host = '127.0.0.1' + this.port = 3000 + } + address () { + return { port: this.port } + } - this.child.on('exit', function onExit (code) { - app.child = null + start (callback) { + const app = this + let done = false + const env = utils.childEnvironment() - if (!done) { - done = true - callback(new Error('Unexpected app exit with code ' + code)) - } - }) + env.PORT = String(app.port) - function tryConnect () { - if (done || !app.child) return + this.child = exec('npm start', { + cwd: this.dir, + env: env + }) - var socket = net.connect(app.port, app.host) + this.child.stderr.pipe(process.stderr, { end: false }) - socket.on('connect', function onConnect () { - socket.end() + this.child.on('exit', function onExit (code) { + app.child = null if (!done) { done = true - callback(null) + callback(new Error('Unexpected app exit with code ' + code)) } }) - socket.on('error', function onError (err) { - socket.destroy() - - if (err.syscall !== 'connect') { - return callback(err) + function tryConnect () { + if (done || !app.child) { + return } - setImmediate(tryConnect) - }) - } + const socket = net.connect(app.port, app.host) - setImmediate(tryConnect) -} + socket.on('connect', function onConnect () { + socket.end() + + if (!done) { + done = true + callback(null) + } + }) + + socket.on('error', function onError (err) { + socket.destroy() + + if (err.syscall !== 'connect') { + return callback(err) + } -AppRunner.prototype.stop = function stop (callback) { - if (!this.child) { - setImmediate(callback) - return + setImmediate(tryConnect) + }) + } + + setImmediate(tryConnect) } - this.child.stderr.unpipe() - this.child.removeAllListeners('exit') + stop (callback) { + if (!this.child) { + setImmediate(callback) + return + } + + this.child.stderr.unpipe() + this.child.removeAllListeners('exit') - kill(this.child.pid, 'SIGTERM', callback) + kill(this.child.pid, 'SIGTERM', callback) - this.child = null + this.child = null + } } + +module.exports = AppRunner diff --git a/test/support/utils.js b/test/support/utils.js index 04b402ae..16295e22 100644 --- a/test/support/utils.js +++ b/test/support/utils.js @@ -1,9 +1,9 @@ 'use strict' -var fs = require('fs') -var os = require('os') -var path = require('path') -var uid = require('uid-safe') +const fs = require('fs') +const os = require('os') +const path = require('path') +const uid = require('uid-safe') module.exports.childEnvironment = childEnvironment module.exports.parseCreatedFiles = parseCreatedFiles @@ -12,10 +12,10 @@ module.exports.stripWarnings = stripWarnings module.exports.tmpDir = tmpDir function childEnvironment () { - var env = Object.create(null) + const env = Object.create(null) // copy the environment except for npm veriables - for (var key in process.env) { + for (let key in process.env) { if (key.substr(0, 4) !== 'npm_') { env[key] = process.env[key] } @@ -25,13 +25,13 @@ function childEnvironment () { } function parseCreatedFiles (output, dir) { - var files = [] - var lines = output.split(/[\r\n]+/) - var match + const files = [] + const lines = output.split(/[\r\n]+/) + let match - for (var i = 0; i < lines.length; i++) { + for (let i = 0; i < lines.length; i++) { if ((match = /create.*?: (.*)$/.exec(lines[i]))) { - var file = match[1] + let file = match[1] if (dir) { file = path.resolve(dir, file) @@ -56,7 +56,7 @@ function stripWarnings (str) { } function tmpDir () { - var dirname = path.join(os.tmpdir(), uid.sync(8)) + const dirname = path.join(os.tmpdir(), uid.sync(8)) fs.mkdirSync(dirname, { mode: parseInt('0700', 8) })