3
3
const assert = require ( 'assert' )
4
4
const Mode = require ( 'stat-mode' )
5
5
const path = require ( 'path' )
6
+ const watcher = require ( './watcher' )
7
+
6
8
const {
7
9
readdir,
8
10
batchAsync,
@@ -26,7 +28,9 @@ const { Debugger, fileLogHandler } = require('./debug')
26
28
27
29
const symbol = {
28
30
env : Symbol ( 'env' ) ,
29
- log : Symbol ( 'log' )
31
+ log : Symbol ( 'log' ) ,
32
+ watch : Symbol ( 'watch' ) ,
33
+ closeWatcher : Symbol ( 'closeWatcher' )
30
34
}
31
35
32
36
/**
@@ -109,6 +113,9 @@ module.exports = Metalsmith
109
113
* @return {Metalsmith }
110
114
*/
111
115
116
+ /**
117
+ * @constructor
118
+ */
112
119
function Metalsmith ( directory ) {
113
120
if ( ! ( this instanceof Metalsmith ) ) return new Metalsmith ( directory )
114
121
assert ( directory , 'You must pass a working directory path.' )
@@ -131,6 +138,16 @@ function Metalsmith(directory) {
131
138
enumerable : false ,
132
139
writable : true
133
140
} )
141
+ Object . defineProperty ( this , symbol . watch , {
142
+ value : false ,
143
+ enumerable : false ,
144
+ writable : true
145
+ } )
146
+ Object . defineProperty ( this , symbol . closeWatcher , {
147
+ value : null ,
148
+ enumerable : false ,
149
+ writable : true
150
+ } )
134
151
}
135
152
136
153
/**
@@ -412,20 +429,22 @@ Metalsmith.prototype.build = function (callback) {
412
429
if ( this [ symbol . log ] . pending ) {
413
430
this [ symbol . log ] . on ( 'ready' , ( ) => resolve ( ) )
414
431
} else {
415
- /* istanbul ignore next */
416
432
resolve ( )
417
433
}
418
434
} )
419
435
}
420
436
} )
421
- . then ( this . process . bind ( this ) )
422
- . then ( ( files ) => {
423
- return this . write ( files ) . then ( ( ) => {
424
- if ( this [ symbol . log ] ) this [ symbol . log ] . end ( )
425
- return files
437
+ . then (
438
+ this . process . bind ( this , ( err , files ) => {
439
+ if ( err ) throw err
440
+ return this . write ( files )
441
+ . then ( ( ) => {
442
+ if ( this [ symbol . log ] ) this [ symbol . log ] . end ( )
443
+ if ( isFunction ( callback ) ) callback ( null , files )
444
+ } )
445
+ . catch ( callback )
426
446
} )
427
- } )
428
-
447
+ )
429
448
/* block required for Metalsmith 2.x callback-flow compat */
430
449
if ( isFunction ( callback ) ) {
431
450
result . then ( ( files ) => callback ( null , files ) , callback )
@@ -434,6 +453,65 @@ Metalsmith.prototype.build = function (callback) {
434
453
}
435
454
}
436
455
456
+ /**
457
+ * **EXPERIMENTAL — Caution**
458
+ * * not to be used with @metalsmith/metadata <= 0.2.0: a bug may trigger an infinite loop
459
+ * * not to be used with existing watch plugins
460
+ * * metalsmith.process/build are **not awaitable** when watching is enabled.
461
+ * Instead of running once at the build's end, callbacks passed to these methods will run on every rebuild.
462
+ *
463
+ * Set the list of paths to watch and trigger rebuilds on. The watch method will skip files ignored with `metalsmith.ignore()`
464
+ * and will do partial (true) or full (false) rebuilds depending on the `metalsmith.clean()` setting.
465
+ * It can be used both for rebuilding in-memory with `metalsmith.process` or writing to file system with `metalsmith.build`,
466
+ * @method Metalsmith#watch
467
+ * @param {boolean|string|string[] } [paths]
468
+ * @return {Metalsmith|Promise<void>|boolean|import('chokidar').WatchOptions }
469
+ * @example
470
+ *
471
+ * metalsmith
472
+ * .ignore(['wont-be-watched']) // ignored
473
+ * .clean(false) // do partial rebuilds
474
+ * .watch(true) // watch all files in metalsmith.source()
475
+ * .watch(['lib','src']) // or watch files in directories 'lib' and 'src'
476
+ *
477
+ * if (process.argv[2] === '--dry-run') {
478
+ * metalsmith.process(onRebuild) // reprocess in memory without writing to disk
479
+ * } else {
480
+ * metalsmith.build(onRebuild) // rewrite to disk
481
+ * }
482
+ *
483
+ * function onRebuild(err, files) {
484
+ * if (err) {
485
+ * metalsmith.watch(false) // stop watching
486
+ * .finally(() => console.log(err)) // and log build error
487
+ * }
488
+ * console.log('reprocessed files', Object.keys(files).join(', ')))
489
+ * }
490
+ */
491
+ Metalsmith . prototype . watch = function ( options ) {
492
+ if ( isUndefined ( options ) ) return this [ symbol . watch ]
493
+ if ( ! options ) {
494
+ // if watch has previously been enabled and is now passed false, close the watcher
495
+ this [ symbol . watch ] = false
496
+ if ( options === false && typeof this [ symbol . closeWatcher ] === 'function' ) {
497
+ return this [ symbol . closeWatcher ] ( )
498
+ }
499
+ } else {
500
+ if ( isString ( options ) || Array . isArray ( options ) ) options = { paths : options }
501
+ else if ( options === true ) options = { paths : this . source ( ) }
502
+
503
+ this [ symbol . watch ] = {
504
+ paths : options . paths ,
505
+ alwaysStat : false ,
506
+ cwd : this . directory ( ) ,
507
+ ignored : this . ignore ( ) ,
508
+ ignoreInitial : true ,
509
+ awaitWriteFinish : true
510
+ }
511
+ }
512
+ return this
513
+ }
514
+
437
515
/**
438
516
* Process files through plugins without writing out files.
439
517
*
@@ -450,15 +528,24 @@ Metalsmith.prototype.build = function (callback) {
450
528
*/
451
529
452
530
Metalsmith . prototype . process = function ( callback ) {
453
- const result = this . read ( this . source ( ) ) . then ( ( files ) => {
454
- return this . run ( files , this . plugins )
455
- } )
531
+ const result = this . read ( this . source ( ) )
456
532
457
- /* block required for Metalsmith 2.x callback-flow compat */
458
- if ( callback ) {
459
- result . then ( ( files ) => callback ( null , files ) , callback )
533
+ if ( this . watch ( ) ) {
534
+ return result . then ( ( files ) => {
535
+ const msWatcher = watcher ( files , this )
536
+ msWatcher ( this [ symbol . watch ] , callback ) . then ( ( close ) => {
537
+ this [ symbol . closeWatcher ] = close
538
+ } )
539
+ } )
460
540
} else {
461
- return result
541
+ result . then ( ( files ) => this . run ( files , this . plugins ) )
542
+
543
+ /* block required for Metalsmith 2.x callback-flow compat */
544
+ if ( callback ) {
545
+ result . then ( ( files ) => callback ( null , files ) , callback )
546
+ } else {
547
+ return result
548
+ }
462
549
}
463
550
}
464
551
0 commit comments