1 | var chalk = require('chalk')
|
2 | var unyield = require('unyield')
|
3 | var chokidar = require('chokidar')
|
4 | var thunkify = require('thunkify')
|
5 | var superstatic = require('superstatic')
|
6 |
|
7 | var join = require('path').join
|
8 | var getport = thunkify(require('get-port'))
|
9 | var spawnLR = thunkify(require('./livereloader').spawnLR)
|
10 | var injectLR = require('./livereloader').injectLR
|
11 | var watchLR = require('./livereloader').watchLR
|
12 | var loadJson = require('./loader')
|
13 | var debounce = require('./debounce')
|
14 |
|
15 | function exists (file) {
|
16 | try {
|
17 | return require('fs').statSync(file)
|
18 | } catch (e) {
|
19 | return false
|
20 | }
|
21 | }
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | var eventSymbols = {
|
28 | add: '+',
|
29 | addDir: '+',
|
30 | change: '◦',
|
31 | unlink: '×',
|
32 | unlinkDir: '×'
|
33 | }
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | function Runner (dir, options) {
|
40 | if (exists(join(dir, 'metalsmith.json'))) {
|
41 | this.metalsmith = loadJson(dir)
|
42 | } else if (exists('metalsmith.js')) {
|
43 | this.metalsmith = require(join(dir, 'metalsmith.js'))
|
44 | } else {
|
45 | throw new Error("Can't find metalsmith.json or metalsmith.js")
|
46 | }
|
47 | this.options = options
|
48 | this.port = options && options.port || process.env.PORT || 3000
|
49 | this.app = undefined
|
50 | this.watcher = undefined
|
51 | this.server = undefined
|
52 | this.tinylr = undefined
|
53 | this.lrport = undefined
|
54 | this.lrwatcher = undefined
|
55 | }
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | Runner.prototype.log = require('./log').log
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | Runner.prototype.start = unyield(function * () {
|
68 | this.log('start: NODE_ENV=' + process.env.NODE_ENV)
|
69 | yield this.build()
|
70 | if (process.env.NODE_ENV !== 'production') {
|
71 | this.watch()
|
72 | }
|
73 | return yield this.serve()
|
74 | })
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | Runner.prototype.close = function () {
|
81 | ['watcher', 'server', 'tinylr', 'lrwatcher'].forEach(function (attr) {
|
82 | if (this[attr]) {
|
83 | this[attr].close()
|
84 | this[attr] = undefined
|
85 | }
|
86 | }.bind(this))
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 | Runner.prototype.serve = unyield(function * () {
|
94 | var ms = this.metalsmith
|
95 | var options = this.options
|
96 |
|
97 | var app = this.app = superstatic({
|
98 | config: {
|
99 | root: ms.destination()
|
100 | },
|
101 | debug: false
|
102 | })
|
103 |
|
104 | if (options.livereload !== false &&
|
105 | process.env.NODE_ENV !== 'production') {
|
106 | yield this.enableLR()
|
107 | }
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | var listen = thunkify(app.listen.bind(app))
|
115 | yield listen(this.port)
|
116 | this.log('serve: listening on http://localhost:' + this.port)
|
117 | })
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | Runner.prototype.requestLogger = function (req, res, next) {
|
124 | this.log('serve: ' + req.method + ' ' + req.url)
|
125 | next()
|
126 | }
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | Runner.prototype.enableLR = unyield(function * () {
|
133 | var ms = this.metalsmith
|
134 | var root = ms.destination()
|
135 |
|
136 | this.lrport = yield getport()
|
137 | this.tinylr = yield spawnLR(this.lrport)
|
138 | this.lrwatcher = watchLR(root, this.tinylr, onChange.bind(this))
|
139 | this.app.use(injectLR(this.lrport))
|
140 | this.log('livereload: listening on port ' + this.lrport)
|
141 |
|
142 | function onChange (files) {
|
143 | this.logFiles('->', eventSymbols.change, ms._destination, files)
|
144 | }
|
145 | })
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | Runner.prototype.logFiles = function (prefix, symbol, root, files) {
|
152 | var maxFiles = 3
|
153 | var pre = prefix + ': ' + (symbol || ' ') + ' '
|
154 | var join = require('path').join
|
155 |
|
156 | files = files.sort()
|
157 |
|
158 | if (files.length <= maxFiles) {
|
159 | files.forEach(function (file) {
|
160 | file = join(root, file)
|
161 | this.log(pre + file)
|
162 | }.bind(this))
|
163 | } else {
|
164 | var file = join(root, files[0])
|
165 | var n = files.length - 1
|
166 | this.log(pre + file + ' (and ' + n + ' others)')
|
167 | }
|
168 | }
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | Runner.prototype.watch = function () {
|
175 | var ms = this.metalsmith
|
176 |
|
177 | this.log('watch: waiting for changes, ^C to abort')
|
178 |
|
179 | function onWatch (argsList) {
|
180 | argsList.forEach(function (args) {
|
181 | var event = args[0]
|
182 | var path = args[1]
|
183 | var symbol = eventSymbols[event] || event
|
184 | this.log('<-: ' + symbol + ' ' + path)
|
185 | }.bind(this))
|
186 |
|
187 | this.build(function () {})
|
188 | }
|
189 |
|
190 | this.watcher = chokidar.watch(ms.directory(), {
|
191 | ignored: ignoreSpec(ms),
|
192 | ignoreInitial: true,
|
193 | cwd: ms.directory()
|
194 | })
|
195 | .on('all', debounce(onWatch.bind(this), 20))
|
196 | }
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | function ignoreSpec (ms) {
|
203 | var dir = ms.directory()
|
204 | var dest = ms.destination()
|
205 |
|
206 | return function (path) {
|
207 | return false ||
|
208 | matches(path, 'node_modules', dir) ||
|
209 | matches(path, 'bower_components', dir) ||
|
210 | matches(path, '.git', dir) ||
|
211 | matches(path, dest, dir)
|
212 | }
|
213 | }
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | function matches (path, parent, base) {
|
220 | if (path.substr(0, 1) !== '/') {
|
221 | path = require('path').join(base, path)
|
222 | }
|
223 |
|
224 | if (parent.substr(0, 1) !== '/') {
|
225 | parent = require('path').join(base, parent)
|
226 | }
|
227 |
|
228 | return (path.substr(0, parent.length) === parent)
|
229 | }
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 | Runner.prototype.build = unyield(function * () {
|
236 | var start = new Date()
|
237 | var ms = this.metalsmith
|
238 | var build = thunkify(ms.build.bind(ms))
|
239 |
|
240 | try {
|
241 | yield build()
|
242 | var duration = new Date() - start
|
243 | this.log(': ' + chalk.green('✓') + ' ' + duration + 'ms')
|
244 | } catch (err) {
|
245 | this.log('err: ' + err.message, 'err')
|
246 | this.log('' + err.stack, 'err')
|
247 | throw err
|
248 | }
|
249 | })
|
250 |
|
251 | module.exports = Runner
|