So this is a short one to just showcase how one of our new features in the CommandBox 3.7 beta works.  To get this version and play with it, simply run the following command and it will direct you to the latest bleeding edge download page:

CommandBox> upgrade --latest

What does it do?

The new command is testbox watch and it will stay running in the foreground and monitor your app for any file changes.  When a file change it detected, it runs your tests from the console and outputs the results so you can get instant feedback on save as to whether you just broke something.

CommandBox> testbox watch

Watching Files...

Executing tests via http://127.0.0.1:59060/tests/runner.cfm?&recurse=true&reporter=json, please wait...
TestBox v2.5.0-snapshot
---------------------------------------------------------------------------------
| Passed  | Failed  | Errored | Skipped | Time    | Bundles | Suites  | Specs   |
---------------------------------------------------------------------------------
| 16      | 0       | 0       | 0       | 132 ms  | 2       | 4       | 16      |
---------------------------------------------------------------------------------

Stopping...

CommandBox>

If you want to see a demo of this in action, check out this screencast I made.  

 

The Code

So I didn't write this post to be a tutorial or really even to get people to try out the feature.  I wanted to put the actual code of the new "testbox watch" command up here just because I really liked it.  I've been working to abstract out common stuff into libraries and have been experimenting with fluent APIs that use a method chaining DSL to create a transient, configure it, and execute it.  There's two such DSLs at play here which I think leave the code nice and readable with no implementation details getting in the way of what's going on.

Command DSL

The first one allows you to execute any other valid command in CommandBox.  It has a bunch of optional methods you can call to pass in flags, parameters, etc which are documented here but in its simplest form it just looks like this:

command( 'version' )
    .run();

This give you an easy way to delegate to another command from inside CFML and still work with parameters and output in a natural way.

Watcher DSL

This is brand new, and provides a generic API to start a configurable directory watcher in any folder complete with file globbing (courtesy of my File Globber module) and provide a closure/UDF to be executed every time the watcher fires.  It looks a bit like this:

watch()
	.paths( '**.cfc' )
	.inDirectory( ... )
	.onChange( function() {
		// Do something
	} )
	.start();

Put It Together

So when you put those together, you get this which is literally every line of code in the CommandBox "testbox watch" command.  

/**
 * @paths Command delimited list of file globbing paths to watch.
 * @delay How may milliseconds to wait before polling for changes
 * @directory The directory to watch for changes. 
 **/
function run(
	string paths='**.cfc',  
 	number delay=500,
 	string directory=''
) {	
	arguments.directory = fileSystemUtil.resolvePath( arguments.directory );
	
	// Tabula rasa
	command( 'cls' ).run();
	
	// Start watcher
	watch()
		.paths( paths.listToArray() )
		.inDirectory( directory )
		.withDelay( delay )
		.onChange( function() {
			
			// Clear the screen
			command( 'cls' )
				.run();
				
			// Run the tests in the target directory
			command( 'testbox run' )
				.inWorkingDirectory( directory )
				.run();
									
		} )
		.start();
}

There's obviously a lot of plumbing stashed away inside...

  1. ... the core of CommandBox that handles parameters and output from the command
  2. ... the command DSL that delegates back to the core parser and executor
  3. ... the watcher that monitors the file system

but you don't see any of it here.  All of these DSLs and helper methods are available when you write custom CommandBox commands with CFML-based CLI scripting.  (** Watcher DSL is still beta on the bleeding edge release)

I hope you enjoyed this peek under the covers and get inspired to make some commands of your own-- or contribute back to the core commands to make them more customizable.