| name | commandbox-developing |
| description | Use this skill for developing CommandBox extensions: creating custom commands (CFCs with run() method), command namespaces, parameters and tab completion, WireBox DI injection, command output with print helper, running sub-commands, creating modules (ModuleConfig.cfc), module conventions (commands/models/interceptors), interceptors, lifecycle events, custom interception points, injection DSL, user settings, and linking modules for development. |
Developing for CommandBox
Overview
CommandBox is fully extensible via CFML modules. You can create custom commands, interceptors, and models — all packaged as reusable modules. Modules live in ~/.CommandBox/cfml/modules/ for personal use or are distributed via ForgeBox.
reload
config set developerMode=true
config set developerMode=false
Custom Commands
Minimal Command
mkdir -p ~/.CommandBox/cfml/modules/myModule/commands
~/.CommandBox/cfml/modules/myModule/ModuleConfig.cfc
component {
function configure() {}
}
~/.CommandBox/cfml/modules/myModule/commands/Hello.cfc
component {
function run( required string name ) {
print.boldGreenLine( "Hello, #name#!" );
}
array function suggestNames( string paramSoFar, struct passedNamedParameters ) {
return [ "Alice", "Bob", "Charlie" ];
}
}
reload
hello Brad
hello --name=Brad
Namespaced Commands
Create subfolders under commands/ to create namespaced commands:
commands/
├── greet/
│ ├── Hello.cfc → box greet hello
│ └── Goodbye.cfc → box greet goodbye
└── deploy/
├── Staging.cfc → box deploy staging
└── Prod.cfc → box deploy prod
Command Parameters
function run(
required string param1,
string param2 = "default",
boolean param3 = false,
string param4 = "small",
string param5,
string param6
) {
print.line( arguments.param1 );
}
WireBox Dependency Injection
All command CFCs are wired via WireBox and have access to services:
component {
property name="artifactService" inject="artifactService";
property name="packageService" inject="packageService";
property name="serverService" inject="serverService";
property name="configService" inject="configService";
property name="forgeBox" inject="ForgeBox";
property name="myModel" inject="myModel@myModule";
function run() {
var artifacts = artifactService.listArtifacts();
for ( var pkg in artifacts ) {
print.boldCyanLine( pkg );
}
}
}
Also available: getInstance() and variables.wirebox:
var svc = getInstance( "artifactService" );
var myObj = wirebox.getInstance( "myModel@myModule" );
Print Helper (Output)
function run() {
print.line( "Normal text" );
print.line();
print.green( "inline green" );
print.greenLine( "green + newline" );
print.redLine( "error" );
print.yellowLine( "warning" );
print.cyanLine( "info" );
print.blueLine( "debug" );
print.boldLine( "bold" );
print.boldGreenLine( "bold green" );
print.MistyRose3Line( "fancy 256-color name" );
print.onBlackWhiteLine( "white text on black bg" );
print.indentedLine( " indented text" );
print.underline( "underlined text" );
print.table(
headers = [ "Package", "Version" ],
data = [ [ "coldbox", "7.0.0" ], [ "testbox", "5.0.0" ] ]
);
print.tree( [
{ label: "root", children: [
{ label: "child1" },
{ label: "child2" }
]}
] );
return "Hello World!";
}
Running Other Commands
function run() {
command( "install coldbox" ).run();
var version = command( "package show version" ).run( returnOutput=true );
command( "server start" )
.params( port=8080, openBrowser=false )
.run();
command( "install coldbox" )
.flag( "verbose" )
.flag( "noSave" )
.run();
var exitCode = command( "testbox run" ).run( returnExitCode=true );
if ( exitCode != 0 ) {
error( "Tests failed!" );
}
}
Error Handling
function run() {
try {
command( "server start" ).run();
} catch ( commandException e ) {
print.redLine( "Command failed: #e.message#" );
setExitCode( 1 );
return;
}
error( "Something went wrong" );
}
Interactivity
function run() {
var name = ask( "What is your name? " );
print.greenLine( "Hello, #name#!" );
var pass = ask( message="Password: ", mask="*" );
if ( confirm( "Are you sure?" ) ) {
print.greenLine( "Confirmed!" );
}
var color = multiSelect()
.setQuestion( "Pick a color:" )
.setOptions( [ "red", "green", "blue" ] )
.ask();
}
Modules
Module Structure
~/.CommandBox/cfml/modules/
└── myModule/
├── ModuleConfig.cfc # Required
├── box.json # Package descriptor (for publishing)
├── commands/ # Custom commands
│ └── MyCommand.cfc
├── models/ # WireBox-managed services
│ └── MyService.cfc
├── interceptors/ # Event interceptors
│ └── MyInterceptor.cfc
└── modules/ # Nested dependency modules
ModuleConfig.cfc
component {
property name="shell" inject="shell";
property name="moduleMapping" inject="moduleMapping@MyModule";
property name="modulePath" inject="modulePath@MyModule";
property name="wirebox" inject="wirebox";
property name="log" inject="logbox:logger:{this}";
function configure() {
settings = {
apiUrl: "https://api.example.com",
timeout: 30,
verbose: false
};
interceptors = [
{
class: "#moduleMapping#.interceptors.MyInterceptor",
properties: { coolness: "max" }
}
];
interceptorSettings = {
customInterceptionPoints: "onMyEvent,onMyOtherEvent"
};
binder.map( "myAlias" ).to( "#moduleMapping#.models.MyService" );
}
function onLoad() {
log.info( "MyModule loaded!" );
}
function onUnLoad() {
log.info( "MyModule unloaded." );
}
function onCLIStart( interceptData ) {
if ( interceptData.shellType == "interactive" && settings.verbose ) {
shell.callCommand( "echo 'MyModule active'" );
}
}
}
User Settings (Module Config)
config set modules.myModule.verbose=true
config set modules.myModule.apiUrl=https://api.staging.example.com
config show modules.myModule.verbose
In commands, inject via WireBox:
property name="settings" inject="commandbox:moduleSettings:myModule";
function run() {
print.line( settings.apiUrl );
}
Linking Modules for Development
cd /path/to/my-module-source
package link
reload
Interceptors
Creating an Interceptor
component {
property name="print" inject="PrintBuffer";
property name="log" inject="logbox:logger:{this}";
function postCommand( interceptData ) {
if ( settings.uppercase ) {
interceptData.results = ucase( interceptData.results );
}
}
function preCommand( interceptData ) {
log.debug( "Running: #interceptData.commandString#" );
}
function onException( interceptData ) {
fileAppend(
expandPath( "~/commandbox-errors.log" ),
"#now()# - #interceptData.exception.message##chr(10)#"
);
}
}
Core Interception Points
| Point | When |
|---|
onCLIStart | Shell starts (interactive or one-off) |
onCLIExit | Shell exits |
preCommand | Before any command runs |
postCommand | After any command runs |
onException | Unhandled exception |
preServerStart | Before server starts |
postServerStart | After server starts |
preServerStop | Before server stops |
postServerStop | After server stops |
preInstall | Before package install |
postInstall | After package install |
prePublish | Before package publish |
postPublish | After package publish |
onPackageInstallation | During install (can modify install path) |
onSystemSettingExpansion | When ${...} placeholder is expanded |
onConfigSettingSave | When config setting is saved |
Custom Interception Points
interceptorSettings = {
customInterceptionPoints: "onDeployStart,onDeployComplete"
};
Announce in a command:
component {
property name="interceptorService" inject="interceptorService";
function run() {
interceptorService.announceInterception( "onDeployStart", {
env: "staging",
timestamp: now()
} );
interceptorService.announceInterception( "onDeployComplete", {
env: "staging",
success: true
} );
}
}
Injection DSL
property inject="shell";
property inject="wirebox";
property inject="logbox";
property inject="logbox:logger:{this}";
property inject="interceptorService";
property inject="commandbox:moduleSettings:myModule";
property inject="commandbox:setting:myKey";
property inject="serverService";
property inject="packageService";
property inject="artifactService";
property inject="forgeBox";
property inject="MyService@myModule";
property inject="moduleMapping@myModule";
property inject="modulePath@myModule";
Sharing Your Module
cd /path/to/my-module
package init slug="commandbox-myplugin" version=1.0.0
package set type=commandbox-modules
package set private=false
config set endpoints.forgebox.APIToken=your-token
publish
Anyone can then install it with:
install commandbox-myplugin