diff options
author | Samy Pessé <samypesse@gmail.com> | 2016-02-18 15:08:34 +0100 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2016-02-18 15:08:34 +0100 |
commit | b99c499d2ea67268760a7c8265a0669b016a56f1 (patch) | |
tree | f7e95ab1096abaf9579bf670205b1fb5b8b848b8 /lib/cli | |
parent | 709b388dfcc641fab25d297618b6ffe49f5cd677 (diff) | |
download | gitbook-b99c499d2ea67268760a7c8265a0669b016a56f1.zip gitbook-b99c499d2ea67268760a7c8265a0669b016a56f1.tar.gz gitbook-b99c499d2ea67268760a7c8265a0669b016a56f1.tar.bz2 |
Add back serve command
Diffstat (limited to 'lib/cli')
-rw-r--r-- | lib/cli/helper.js | 4 | ||||
-rw-r--r-- | lib/cli/index.js | 109 | ||||
-rw-r--r-- | lib/cli/server.js | 95 | ||||
-rw-r--r-- | lib/cli/watch.js | 42 |
4 files changed, 248 insertions, 2 deletions
diff --git a/lib/cli/helper.js b/lib/cli/helper.js index e8a049f..1086b4d 100644 --- a/lib/cli/helper.js +++ b/lib/cli/helper.js @@ -63,5 +63,7 @@ module.exports = { options: { log: LOG_OPTION, format: FORMAT_OPTION - } + }, + + FORMATS: FORMATS }; diff --git a/lib/cli/index.js b/lib/cli/index.js index c9a2e8f..0492c29 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -1,7 +1,16 @@ +/* eslint-disable no-console */ + var path = require('path'); +var tinylr = require('tiny-lr'); +var Promise = require('../utils/promise'); var PluginsManager = require('../plugins'); +var Book = require('../book'); +var NodeFS = require('../fs/node'); + var helper = require('./helper'); +var Server = require('./server'); +var watch = require('./watch'); module.exports = { commands: [ @@ -60,8 +69,106 @@ module.exports = { return output.generate(); }); }) - } + }, + + { + name: 'serve [book]', + description: 'Build then serve a book from a directory', + options: [ + { + name: 'port', + description: 'Port for server to listen on', + defaults: 4000 + }, + { + name: 'lrport', + description: 'Port for livereload server to listen on', + defaults: 35729 + }, + { + name: 'watch', + description: 'Enable/disable file watcher', + defaults: true + }, + helper.options.format, + helper.options.log + ], + exec: function(args, kwargs) { + var input = args[0] || process.cwd(); + var server = new Server(); + + // Init livereload server + var lrServer = tinylr({}); + var port = kwargs.port; + var lrPath; + + var generate = function() { + // Stop server if running + if (server.isRunning()) console.log('Stopping server'); + return server.stop() + + // Generate the book + .then(function() { + var book = new Book({ + fs: new NodeFS(), + root: input, + 'config': { + 'defaultsPlugins': ['livereload'] + }, + 'logLevel': kwargs.log + }); + + return book.parse() + .then(function() { + var Out = helper.FORMATS[kwargs.format]; + var output = new Out(book); + + return output.generate() + .thenResolve(output); + }); + }) + + // Start server and watch changes + .then(function(output) { + console.log(); + console.log('Starting server ...'); + return server.start(output.root(), port) + .then(function() { + console.log('Serving book on http://localhost:'+port); + + if (lrPath) { + // trigger livereload + lrServer.changed({ + body: { + files: [lrPath] + } + }); + } + + if (!kwargs.watch) return; + + return watch(output.book.root) + .then(function(filepath) { + // set livereload path + lrPath = filepath; + console.log('Restart after change in file', filepath); + console.log(''); + return generate(); + }); + }); + }); + }; + + return Promise.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport) + .then(function() { + console.log('Live reload server started on port:', kwargs.lrport); + console.log('Press CTRL+C to quit ...'); + console.log(''); + return generate(); + }); + } + } ] }; diff --git a/lib/cli/server.js b/lib/cli/server.js new file mode 100644 index 0000000..3bd5d18 --- /dev/null +++ b/lib/cli/server.js @@ -0,0 +1,95 @@ +var events = require('events'); +var http = require('http'); +var send = require('send'); +var util = require('util'); +var url = require('url'); + +var Promise = require('../utils/promise'); + +var Server = function() { + this.running = null; + this.dir = null; + this.port = 0; + this.sockets = []; +}; +util.inherits(Server, events.EventEmitter); + +// Return true if the server is running +Server.prototype.isRunning = function() { + return !!this.running; +}; + +// Stop the server +Server.prototype.stop = function() { + var that = this; + if (!this.isRunning()) return Promise(); + + var d = Promise.defer(); + this.running.close(function(err) { + that.running = null; + that.emit('state', false); + + if (err) d.reject(err); + else d.resolve(); + }); + + for (var i = 0; i < this.sockets.length; i++) { + this.sockets[i].destroy(); + } + + return d.promise; +}; + +Server.prototype.start = function(dir, port) { + var that = this, pre = Promise(); + port = port || 8004; + + if (that.isRunning()) pre = this.stop(); + return pre + .then(function() { + var d = Promise.defer(); + + that.running = http.createServer(function(req, res){ + // Render error + function error(err) { + res.statusCode = err.status || 500; + res.end(err.message); + } + + // Redirect to directory's index.html + function redirect() { + res.statusCode = 301; + res.setHeader('Location', req.url + '/'); + res.end('Redirecting to ' + req.url + '/'); + } + + // Send file + send(req, url.parse(req.url).pathname) + .root(dir) + .on('error', error) + .on('directory', redirect) + .pipe(res); + }); + + that.running.on('connection', function (socket) { + that.sockets.push(socket); + socket.setTimeout(4000); + socket.on('close', function () { + that.sockets.splice(that.sockets.indexOf(socket), 1); + }); + }); + + that.running.listen(port, function(err) { + if (err) return d.reject(err); + + that.port = port; + that.dir = dir; + that.emit('state', true); + d.resolve(); + }); + + return d.promise; + }); +}; + +module.exports = Server; diff --git a/lib/cli/watch.js b/lib/cli/watch.js new file mode 100644 index 0000000..36c42cc --- /dev/null +++ b/lib/cli/watch.js @@ -0,0 +1,42 @@ +var _ = require('lodash'); +var path = require('path'); +var chokidar = require('chokidar'); +var parsers = require('gitbook-parsers'); + +var Promise = require('../utils/promise'); + +// Watch a folder and resolve promise once a file is modified +function watch(dir) { + var d = Promise.defer(); + dir = path.resolve(dir); + + var toWatch = [ + 'book.json', 'book.js' + ]; + + // Watch all parsable files + _.each(parsers.extensions, function(ext) { + toWatch.push('**/*'+ext); + }); + + var watcher = chokidar.watch(toWatch, { + cwd: dir, + ignored: '_book/**', + ignoreInitial: true + }); + + watcher.once('all', function(e, filepath) { + watcher.close(); + + d.resolve(filepath); + }); + watcher.once('error', function(err) { + watcher.close(); + + d.reject(err); + }); + + return d.promise; +} + +module.exports = watch; |