Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 177 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

3.0.0-SNAPSHOT

INTRODUCTION

Loading...

Loading...

Loading...

Loading...

Loading...

Frameworks and Wheels

Loading...

Loading...

Loading...

Loading...

Command Line Tools

Loading...

Loading...

Loading...

Core Commands

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Code Generation

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Database Commands

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Testing Commands

Loading...

Loading...

Loading...

Loading...

Configuration Commands

Loading...

Loading...

Loading...

Environment Management

Loading...

Loading...

Loading...

Loading...

Plugin Management

Loading...

Loading...

Loading...

Loading...

Code Analysis

Loading...

Loading...

Loading...

Loading...

Security Commands

Loading...

Loading...

Performance Commands

Loading...

Loading...

Documentation Commands

Loading...

Loading...

Loading...

CI/CD Commands

Loading...

Docker Commands

Loading...

Loading...

Deployment Commands

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Beginner Tutorial: Hello World

In this tutorial, we'll be writing a simple application to make sure we have Wheels installed properly and that everything is working as it should.

Testing Your Install

Okay, so you have Wheels installed and can see the Wheels "Congratulations!" page as shown below. That wasn't that hard now, was it?

Hello World: Your First Wheels App

Okay, let's get to some example code. We know that you've been dying to get your hands on some code!

To continue with Programming Tutorial Tradition, we'll create the ubiquitous Hello World! application. But to keep things interesting, let's add a little Wheels magic along the way.

Setting up the Controller

Let's create a controller from scratch to illustrate how easy it is to set up a controller and plug it into the Wheels framework.

First, create a file called Say.cfc in the app/controllers directory and add the code below to the file.

app/controllers/Say.cfc
component extends="Controller"{
}

Congratulations, you just created your first Wheels controller! What does this controller do, you might ask? Well, to be honest, not much. It has no methods defined, so it doesn't add any new functionality to our application. But because it extends the base Controller component, it inherits quite a bit of powerful functionality and is now tied into our Wheels application.

The error says "Could not find the view page for the 'index' action in the 'say' controller." Where did "index" come from? The URL we typed in only specified a controller name but no action. When an action is not specified in the URL, Wheels assumes that we want the default action. Out of the box, the default action in Wheels is set to index. So in our example, Wheels tried to find the index action within the say controller, and it threw an error because it couldn't find its view page.

Setting up an Action

But let's jump ahead. Now that we have the controller created, let's add an action to it called hello. Change your say controller so it looks like the code block below:

app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
    }
}

As you can see, we created an empty method named hello.

Now let's call our new action in the browser and see what we get. To call the hello action, we simply add /hello to the end of the previous URL that we used to call our say controller:

http://127.0.0.1:60000/say/hello

Once again, we get a ColdFusion error. Although we have created the controller and added the hello action to it, we haven't created the view.

Setting up the View

By default, when an action is called, Wheels will look for a view file with the same name as the action. It then hands off the processing to the view to display the user interface. In our case, Wheels tried to find a view file for our say/hello action and couldn't find one.

Let's remedy the situation and create a view file. View files are simple CFML pages that handle the output of our application. In most cases, views will return HTML code to the browser. By default, the view files will have the same name as our controller actions and will be grouped into a directory under the view directory. This new directory will have the same name as our controller.

Find the views directory inside the app directory, located at the root of your Wheels installation. There will be a few directories in there already. For now, we need to create a new directory in the views directory called say. This is the same name as the controller that we created above.

Now inside the say directory, create a file called hello.cfm. In the hello.cfm file, add the following line of code:

app/views/say/hello.cfm
<h1>Hello World!</h1>

Save your hello.cfm file, and let's call our say/hello action once again. You have your first working Wheels page if your browser looks like Figure 3 below.

You have just created your first functional Wheels page, albeit it is a very simple one. Pat yourself on the back, go grab a snack, and when you're ready, let's go on and extend the functionality of our Hello World! application a little more.

Adding Dynamic Content to Your View

We will add some simple dynamic content to our hello action and add a second action to the application. We'll then use some Wheels code to tie the 2 actions together. Let's get get to it!

The Dynamic Content

The first thing we are going to do is to add some dynamic content to our say/hello action. Modify your say controller so it looks like the code block below:

app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
        time = Now();
    }
}

All we are doing here is creating a variable called time and setting its value to the current server time using the basic ColdFusion Now() function. When we do this, the variable becomes immediately available to our view code.

Why not just set up this value directly in the view? If you think about it, maybe the logic behind the value of time may eventually change. What if eventually we want to display its value based on the user's time zone? What if later we decide to pull it from a web service instead? Remember, the controller is supposed to coordinate all of the data and business logic, not the view.

Displaying the Dynamic Content

Next, we will modify our say/hello.cfm view file so that it looks like the code block below. When we do this, the value will be displayed in the browser.

app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>

call your say/hello action again in your browser. Your browser should look like Figure 4 below.

This simple example showed that any dynamic content created in a controller action is available to the corresponding view file. In our application, we created a time variable in the say/hello controller action and display that variable in our say/hello.cfm view file.

Adding a Second Action: Goodbye

Now we will expand the functionality of our application once again by adding a second action to our say controller. If you feel adventurous, go ahead and add a goodbye action to the say controller on your own, then create a goodbye.cfm view file that displays a "Goodbye" message to the user. If you're not feeling that adventurous, we'll quickly go step by step.

First, modify the the say controller file so that it looks like the code block below.

app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
        time = Now();
    }

    function goodbye() {
    }
}

Now go to the app/views/say directory and create a goodbye.cfm page.

Add the following code to the goodbye.cfm page and save it.

app/views/say/goodbye.cfm
Goodbye World!

If we did everything right, we should be able to call the new say/goodbye action using the following URL:

http://127.0.0.1:60000/say/goodbye

Your browser should look like Figure 5 below:

Linking to Other Actions

Now let's link our two actions together. We will do this by adding a link to the bottom of each page so that it calls the other page.

Linking Hello to Goodbye

Open the say/hello.cfm view file. We are going to add a line of code to the end of this file so our say/hello.cfm view file looks like the code block below:

app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>
<p>Time to say <cfoutput>#linkTo(text="goodbye", action="goodbye")#?</cfoutput></p>

Once you have added the additional line of code to the end of the say/hello.cfm view file, save your file and call the say/hello action from your browser. Your browser should look like Figure 6 below.

You can see that Wheels created a link for us and added an appropriate URL for the say/goodbye action to the link.

Linking Goodbye to Hello

Let's complete our little app and add a corresponding link to the bottom of our say/goodbye.cfm view page.

Open your say/goodbye.cfm view page and modify it so it looks like the code block below.

CFML: app/views/say/goodbye.cfm

app/views/say/goodbye.cfm
<h1>Goodbye World!</h1>
<p>Time to say <cfoutput>#linkTo(text="hello", action="hello")#?</cfoutput></p>

If you now call the say/goodbye action in your browser, your browser should look like Figure 7 below.

Much More to Learn

You now know enough to be dangerous with Wheels. Look out! But there are many more powerful features to cover. You may have noticed that we haven't even talked about the M in MVC.

No worries. We will get there. And we think you will enjoy it.

Getting Started

Install Wheels and get a local development server running

One module that we have created is a module that extends CommandBox itself with commands and features specific to the Wheels framework. The Wheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the Wheels developer.

Install CommandBox

Once installed, you can either double-click on the box executable which opens the CommandBox shell window, or run box from a CMD window in Windows, Terminal window in MacOS, or shell prompt on a Linux server. Sometimes you only want to call a single CommandBox command and don't need to launch a whole CommandBox shell window to do that, for these instances you can call the CommandBox command directly from your default system terminal window by prefixing the command with the box prefix.

So to run the CommandBox version command you could run box version from the shell or you could launch the CommandBox shell and run version inside it.

box version

version

This is a good concept to grasp, cause depending on your workflow, you may find it easier to do one versus the other. Most of the commands you will see in these CLI guides will assume that you are entering the command in the actual CommandBox shell so the box prefix is left off.

Install the wheels-cli CommandBox Module

Okay, now that we have CommandBox installed, let's add the Wheels CLI module.

install wheels-cli

Installing this module will add a number of commands to your default CommandBox installation. All of these commands are prefixed by the wheels name space. There are commands to create a brand new Wheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.

Start a new Application using the Wizard

To install a new application using version 3.0, we can use the new application wizard and select Bleeding Edge when prompted to select the template to use.

wheels new

Start a New Application Using the Command Line

Now that we have CommandBox installed and extended it with the Wheels CLI module, let's start our first Wheels app from the command line. We'll look at the simplest method for creating a Wheels app and starting our development server.

wheels generate app myApp server start

A few minutes after submitting the above commands a new browser window should open up and display the default Wheels congratulations screen.

So what just happened? Since we only passed the application name myApp to the wheels generate app command, it used default values for most of its parameters and downloaded our Base template (wheels-base-template) from ForgeBox.io, then downloaded the framework core files (wheels.dev) from ForgeBox.io and placed it in the vendor/wheels directory, then configured the application name and reload password, and started a Lucee server on a random port.

A Word About Command Aliases

CommandBox commands have the capability to be called by multiple names or aliases. The command above wheels generate app can also be initiated by typing wheels g app. In fact g is an alias for generate so wherever you see a command in the CLI documentation that has generate in it you can substitute g instead.

In addition to shortening generate to g, aliases can completely change the name space as well. A command that you haven't seen yet is the wheels generate app-wizard command. This command guides the user through a series of menu options, building up all the parameters needed to customize the start of a new Wheels project. You're likely to use the wizard when starting a new Wheels application so it's good to become familiar with it.

This command has the normal alias referenced above at wheels g app-wizard but it also has an additional alias at wheels new which is the command more prevalent in the Rails community. So the three commands wheels generate app-wizard, wheels g app-wizard, and wheels new all call the same functionality which guides the user though a set of menus, collecting details on how to configure the desired app. Once all the parameters have been gathered, this command actually calls the wheels generate app command to create the actual Wheels application.

This Getting Started guide has taken you from the very beginning and gotten you to the point where you can go into any empty directory on your local development machine and start a Wheels project by issuing a couple of CLI commands. In later guides we'll explore these options further and see what else the CLI can do for us.

Beginner Tutorial: Hello Database

A quick tutorial that demonstrates how quickly you can get database connectivity up and running with Wheels.

Wheels's built in model provides your application with some simple and powerful functionality for interacting with databases. To get started, you will make some simple configurations, call some functions within your controllers, and that's it. Best yet, you will rarely ever need to write SQL code to get those redundant CRUD tasks out of the way.

Our Sample Application: User Management

We'll learn by building part of a sample user management application. This tutorial will teach you the basics of setting up a resource that interacts with the Wheels ORM.

Download source code

Setting up the Data Source

By default, Wheels will connect to a data source wheels.dev. To change this default behavior, open the file at app/config/settings.cfm. In a fresh install of Wheels, you'll see the follwing code:

app/config/settings.cfm
<cfscript>
	/*
		Use this file to configure your application.
		You can also use the environment specific files (e.g. app/config/production/settings.cfm) to override settings set here.
		Don't forget to issue a reload request (e.g. reload=true) after making changes.
		See https://guides.wheels.dev/2.5.0/v/3.0.0-snapshot/working-with-wheels/configuration-and-defaults for more info.
	*/

	/*
		You can change the "wheels.dev" value from the two functions below to set your datasource.
		You can change the the value for the "dataSourceName" to set a default datasource to be used throughout your application.
		You can also change the value for the "coreTestDataSourceName" to set your testing datasource.
	*/
	set(coreTestDataSourceName="wheels.dev");
	set(dataSourceName="wheels.dev");
    // set(dataSourceUserName="");
    // set(dataSourcePassword="");

	/*
		If you comment out the following line, Wheels will try to determine the URL rewrite capabilities automatically.
		The "URLRewriting" setting can bet set to "on", "partial" or "off".
		To run with "partial" rewriting, the "cgi.path_info" variable needs to be supported by the web server.
		To run with rewriting set to "on", you need to apply the necessary rewrite rules on the web server first.
	*/
	set(URLRewriting="On");

	// Reload your application with ?reload=true&password=wheels.dev
	set(reloadPassword="wheels.dev");

	// CLI-Appends-Here
</cfscript>

These lines provide Wheels with the necessary information about the data source, URL rewriting, and reload password for your application, and include the appropriate values. This may include values for dataSourceName, dataSourceUserName, and dataSourcePassword. More on URL rewriting and reload password later.

app/config/settings.cfm
set(dataSourceName="back2thefuture");
// set(dataSourceUserName="marty");
// set(dataSourcePassword="mcfly");

Our Sample Data Structure

Wheels supports MySQL, SQL Server, PostgreSQL, and H2. It doesn't matter which DBMS you use for this tutorial; we will all be writing the same CFML code to interact with the database. Wheels does everything behind the scenes that needs to be done to work with each DBMS.

That said, here's a quick look at a table that you'll need in your database, named users:

Column Name
Data Type
Extra

id

int

auto increment

username

varchar(100)

email

varchar(255)

passwd

varchar(15)

Note a couple things about this users table:

  1. The table name is plural.

  2. The table has an auto-incrementing primary key named id.

Fortunately, there are ways of going outside of these conventions when you really need it. But let's learn the conventional way first. Sometimes you need to learn the rules before you can know how to break them.

Creating Routes for the users Resource

Next, open the file at app/config/routes.cfm. You will see contents similar to this:

app/config/routes.cfm
mapper()
    .wildcard()
    .root(method = "get")
.end();

We are going to create a section of our application for listing, creating, updating, and deleting user records. In Wheels routing, this requires a plural resource, which we'll name users.

Because a users resource is more specific than the "generic" routes provided by Wheels, we'll list it first in the chain of mapper method calls:

app/config/routes.cfm
mapper()
    .resources("users")
    .wildcard()
    .root(method = "get")
.end();

This will create URL endpoints for creating, reading, updating, and deleting user records:

Name
Method
URL Path
Description

users

GET

/users

Lists users

newUsers

GET

/users/new

Display a form for creating a user record

users

POST

/users

Form posts a new user record to be created

editUser

GET

/users/[id]/edit

Displays a form for editing a user record

user

PATCH

/users/[id]

Form posts an existing user record to be updated

user

DELETE

/users/[id]

Deletes a user record

  • Name is referenced in your code to tell Wheels where to point forms and links.

  • Method is the HTTP verb that Wheels listens for to match up the request.

  • URL Path is the URL that Wheels listens for to match up the request.

Don't forget to reload

You will need to reload your application after adding new routes!

Creating Users

First, let's create a simple form for adding a new user to the users table. To do this, we will use Wheels's form helper functions. Wheels includes a whole range of functions that simplifies all of the tasks that you need to display forms and communicate errors to the user.

Creating the Form

Now create a new file in app/views/users called new.cfm. This will contain the view code for our simple form.

Next, add these lines of code to the new file:

app/views/users/new.cfm
<cfoutput>

<h1>New User</h1>

#startFormTag(route="users")#
    <div>
        #textField(objectName="user", property="username", label="Username")#
    </div>

    <div>
        #textField(objectName="user", property="email", label="Email")#
    </div>

    <div>
        #passwordField(
            objectName="user",
            property="passwd",
            label="Password"
        )#
    </div>

    <div>#submitTag()#</div>
#endFormTag()#

</cfoutput>

Form Helpers

What we've done here is use form helpers to generate all of the form fields necessary for creating a new user in our database. It may feel a little strange using functions to generate form elements, but it will soon become clear why we're doing this. Trust us on this one… you'll love it!

Supplying the Form with Data

All of the form helper calls in our view specify an objectName argument with a reference to a variable named user. That means that we need to supply our view code with an object called user. Because the controller is responsible for providing the views with data, we'll set it there.

Create a new ColdFusion component at app/controllers/Users.cfc.

app/controllers/Users.cfc
component extends="Controller" {
    function config(){}

    function new() {
        user = model("user").new();
    }
}

Wheels will automatically know that we're talking about the users database table when we instantiate a user model. The convention: database tables are plural and their corresponding Wheels models are singular.

Why is our model name singular instead of plural? When we're talking about a single record in the users database, we represent that with an individual model object. So the users table contains many user objects. It just works better in conversation.

The Generated Form

Now when we run the URL at http://localhost/users/new, we'll see the form with the fields that we defined.

The HTML generated by your application will look something like this:

/users/new
<h1>New User</h1>

<form action="/users" method="post">
    <div>
        <label for="user-username">
            Username
            <input id="user-username" type="text" value="" name="user&#x5b;username&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-email">
            Email
            <input id="user-email" type="text" value="" name="user&#x5b;email&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-passwd">
            Password
            <input id="user-passwd" type="password" value="" name="user&#x5b;passwd&#x5d;">
        </label>
    </div>

    <div><input value="Save&#x20;changes" type="submit"></div>
</form>

So far we have a fairly well-formed, accessible form, without writing a bunch of repetitive markup.

Handling the Form Submission

Next, we'll code the create action in the controller to handle the form submission and save the new user to the database.

app/controllers/Users.cfc
function create() {
    user = model("user").create(params.user);

    redirectTo(
        route="users",
        success="User created successfully."
    );
}

Because we used the objectName argument in the fields of our form, we can access the user data as a struct in the params struct.

There are more things that we can do in the create action to handle validation, but let's keep it simple in this tutorial.

Listing Users

First, let's get the data that the listing needs. Create an action named index in the users controller like so:

app/controllers/Users.cfc
function index() {
    users = model("user").findAll(order="username");
}

In the view at app/views/users/index.cfm, it's as simple as looping through the query and outputting the data

app/views/users/index.cfm
<cfoutput>

<h1>Users</h1>

<p>#linkTo(text="New User", route="newUser")#</p>

<table>
    <thead>
        <tr>
            <th>Username</th>
            <th>Email</th>
            <th colspan="2"></th>
        </tr>
    </thead>
    <tbody>
        <cfloop query="users">
            <tr>
                <td>
                    #EncodeForHtml(users.username)#
                </td>
                <td>
                    #EncodeForHtml(users.email)#
                </td>
                <td>
                    #linkTo(
                        text="Edit",
                        route="editUser",
                        key=users.id,
                        title="Edit #users.username#"
                    )#
                </td>
                <td>
                    #buttonTo(
                        text="Delete",
                        route="user",
                        key=users.id,
                        method="delete",
                        title="Delete #users.username#"
                    )#
                </td>
            </tr>
        </cfloop>
    </tbody>
</table>

</cfoutput>

When to use EncodeForHtml

You'll see references to EncodeForHtml in some of our examples that output data. This helps escape HTML code in data that attackers could use to embed inject harmful JavaScript. (This is commonly referred to as an "XSS attack," short for "Cross-site Scripting attack.")

A rule of thumb: you do not need to use EncodeForHtml when passing values into Wheels helpers like linkTo, buttonTo, startFormTag, textField, etc. However, you need to escape data that is displayed directly onto the page without a Wheels helper.

Editing Users

We'll now show another cool aspect of form helpers by creating a screen for editing users.

Coding the Edit Form

You probably noticed in the code listed above that we'll have an action for editing a single users record. We used the linkTo() form helper function to add an "Edit" button to the form. This action expects a key as well.

Because in the linkTo() form helper function we specified the parameter key, Wheels adds this parameter into the URL when generating the route.

Wheels will automatically add the provided 'key' from the URL to the params struct in the controllers edit() function.

Given the provided key, we'll have the action load the appropriate user object to pass on to the view:

app/controllers/Users.cfc
function edit() {
    user = model("user").findByKey(params.key);
}

The view at app/views/users/edit.cfm looks almost exactly the same as the view for creating a user:

app/views/users/edit.cfm
<cfoutput>

<h1>Edit User #EncodeForHtml(user.username)#</h1>

#startFormTag(route="user", key=user.key(), method="patch")#
    <div>
        #textField(objectName="user", property="username", label="Username")#
    </div>

    <div>
        #textField(objectName="user", property="email", label="Email")#
    </div>

    <div>
        #passwordField(
            objectName="user",
            property="passwd",
            label="Password"
        )#
    </div>

    <div>#submitTag()#</div>
#endFormTag()#

</cfoutput>

But an interesting thing happens. Because the form fields are bound to the user object via the form helpers' objectName arguments, the form will automatically provide default values based on the object's properties.

With the user model populated, we'll end up seeing code similar to this:

app/views/users/edit.cfm
<h1>Edit User Homer Simpson</h1>

<form action="/users/1" method="post">
    <input type="hidden" name="_method" value="patch">

    <div>
        <input type="hidden" name="user&#x5b;id&#x5d;" value="15">
    </div>

    <div>
        <label for="user-username">
            Name
            <input
                id="user-username"
                type="text"
                value="Homer Simpson"
                name="user&#x5b;username&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-email">
            Email
            <input
                id="user-email"
                type="text"
                value="homerj@nuclearpower.com"
                name="user&#x5b;email&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-passwd">
            Password
            <input
                id="user-passwd"
                type="password"
                value="donuts.mmm"
                name="user&#x5b;passwd&#x5d;">
        </label>
    </div>

    <div><input value="Save&#x20;changes" type="submit"></div>
</form>

Pretty cool, huh?

Opportunities for Refactoring

There's a lot of repetition in the new and edit forms. You'd imagine that we could factor out most of this code into a single view file. To keep this tutorial from becoming a book, we'll just continue on knowing that this could be better.

Handing the Edit Form Submission

Now we'll create the update action. This will be similar to the create action, except it will be updating the user object:

app/controllers/Users.cfc
function update() {
    user = model("user").findByKey(params.key);
    user.update(params.user);

    redirectTo(
        route="editUser",
        key=user.id,
        success="User updated successfully."
    );
}

Deleting Users

Notice in our listing above that we have a delete action. Here's what it would look like:

app/controllers/Users.cfc
function delete() {
    user = model("user").findByKey(params.key);
    user.delete();

    redirectTo(
        route="users",
        success="User deleted successfully."
    );
}

Database Says Hello

We've shown you quite a few of the basics in getting a simple user database up and running. We hope that this has whet your appetite to see some of the power packed into the Wheels framework. There's plenty more.

Be sure to read on to some of the related chapters listed below to learn more about working with Wheels's ORM.

Running Local Development Servers

Starting a local development server

With CommandBox, we don't need to have Lucee or Adobe ColdFusion installed locally. With a simple command, we can make CommandBox go and get the CFML engine we've requested, and quickly create a server running on Undertow. Make sure you're in the root of your website, and then run:

server start

The server will then start on a random port on 127.0.0.1 based the configuration from the server.json file that is in the root of your application that comes with wheels. We can add various options to server.json to customize our server. Your default server.json will look something like this:

{
    "name":"wheels",
    "web":{
        "host":"localhost",
        "webroot":"public",
        "rewrites":{
            "enable":true,
            "config":"public/urlrewrite.xml"
        }
    },
    "app":{
        "cfengine":"lucee"
    }
}

In this server.json file, the server name is set to wheels, meaning I can now start the server from any directory by simply calling start myApp. We don't have any port specified, but you can specify any port you want. Lastly, we have URL rewriting enabled and pointed the URL rewrite configuration file to public/urlrewrite.xml, which is included starting from Wheels 2.x.

Using custom host names

Controlling local servers

Obviously, anything you start, you might want to stop. Servers can be stopped either via right/ctrl clicking on the icon in the taskbar, or by the stop command. To stop a server running in the current directory issue the following:

server stop

You can also stop a server from anywhere by using its name:

server stop myApp

If you want to see what server configurations exist on your system and their current status, simply do server list

server list

myapp (stopped)
 http://127.0.0.1:60000
 Webroot: /Users/wheels/Documents/myapp

myAPI (stopped)
 http://127.0.0.1:60010
 Webroot: /Users/wheels/Documents/myAPI

megasite (stopped)
 http://127.0.0.1:61280
 CF Engine: lucee 4.5.4+017
 Webroot: /Users/wheels/Documents/megasite

awesomesite (stopped)
 http://127.0.0.1:60015
 CF Engine: lucee 4.5.4+017
 Webroot: /Users/wheels/Documents/awesomeo

To remove a server configuration from the list, you can use server forget myapp. Note the status of the servers on the list are somewhat unreliable, as it only remembers the last known state of the server: so if you start a server and then turn off your local machine, it may still remember it as running when you turn your local machine back on, which is why we recommend the use of force: true in the server.json file.

Specifying different CF engines

By default, CommandBox will run Lucee (version 6.x at time of writing). You may wish to specify an exact version of Lucee, or use Adobe ColdFusion. We can do this via either setting the appropriate cfengine setting in server.json, or at runtime with the cfengine= argument.

Start the default engine

CommandBox> start

__

Start the latest stable Lucee 5.x engine

CommandBox> start cfengine=lucee@5

__

Start a specific engine and version

CommandBox> start cfengine=adobe@10.0.12

__

Start the most recent Adobe server that starts with version "11"

CommandBox> start cfengine=adobe@11

__

Start the most recent adobe engine that matches the range

CommandBox> start cfengine="adobe@>9.0 <=11"

Or via server.json

{
    "name":"myApp",
    "force":true,
    "web":{
        "http":{
            "host":"localhost",
            "port":60000
        },
        "rewrites":{
            "enable":true,
            "config":"urlrewrite.xml"
        }
    },
    "app":{
        "cfengine":"adobe@2018"
    },
}

CFIDE / Lucee administrators

The default username and password for all administrators is admin & commandbox

You can of course run multiple servers, so if you need to test your app on Lucee 5.x, Lucee 6.x and Adobe 2018, you can just start three servers with different cfengine= arguments!

Watch out

CommandBox 5.1 required to install dependencies easily

By default, the Lucee server that CommandBox starts includes all the essential Lucee extensions you need, but if need to minimize the size of the Lucee instance you launch, then you can use Lucee-Light by specifying cfengine=lucee-light in your server.json file. Wheels can run just fine on lucee-light (which is after all, Lucee, minus all the extensions) but at a minimum, requires the following extensions to be installed as dependencies in your box.json. Please note you may have to add any drivers you need for your database to this list as well.

"dependencies":{
    "lucee-image":"lex:https://ext.lucee.org/lucee.image.extension-1.0.0.35.lex",
    "lucee-zip": "lex:https://ext.lucee.org/compress-extension-1.0.0.2.lex",
    "lucee-esapi": "lex:https://ext.lucee.org/esapi-extension-2.1.0.18.lex"
}

Once added to your box.json file, while the server is running, just do box install, which will install the dependencies, and load them into the running server within 60 seconds.

Alternatively you can download the extensions and add them manually to your server root's deploy folder (i.e \WEB-INF\lucee-server\deploy)

Tutorial: Wheels, AJAX, and You

Using Wheels to develop web applications with AJAX features is a breeze. You have several options and tools at your disposal, which we'll cover in this chapter.

Wheels was designed to be as lightweight as possible, so this keeps your options fairly open for developing AJAX features into your application.

For this tutorial, let's create the simplest example of all: a link that will render a message back to the user without refreshing the page.

A Simple Example

In this example, we'll wire up some simple JavaScript code that calls a Wheels action asynchronously. All of this will be done with basic jQuery code and built-in Wheels functionality.

First, let's make sure we've got an appropriate route setup. It might be you're still using the default wildcard() route which will create some default GET routes for the controller/action pattern, but we'll add a new route here just for practice. We are going to create a route named sayHello and direct it to the hello action of the say controller. There are two ways you could write this code a long hand method specifying the controller and action separately as well as a short hand method that combines the two into a single parameter.

The longhand way would look like:

app/config/routes.cfm
mapper()
  .get(name="sayHello", controller="say", action="hello")
.end()

The shorthand method would look like:

app/config/routes.cfm
mapper()
  .get(name="sayHello", to="say##hello")
.end()

You can decide which method you prefer. Both sets of code above are equivalent.

Then, let's create a link to a controller's action in a view file, like so:

app/views/say/hello.cfm
<cfoutput>

<!--- View code --->
<h1></h1>
<p></p>

#linkTo(text="Alert me!", route="sayHello", id="alert-button")#

</cfoutput>

That piece of code by itself will work just like you expect it to. When you click the link, you will load the hello action inside the say controller.

JavaScript
(function($) {
    // Listen to the "click" event of the "alert-button" link and make an AJAX request
    $("#alert-button").on("click", function(event) {
        $.ajax({
            type: "GET",
            // References "/say/hello?format=json"
            url: $(this).attr("href") + "?format=json",
            dataType: "json",
            success: function(response) {
                $("h1").html(response.message);
                $("p").html(response.time);
            }
        });

        event.preventDefault();
    });
})(jQuery);

With that code, we are listening to the click event of the hyperlink, which will make an asynchronous request to the hello action in the say controller. Additionally, the JavaScript call is passing a URL parameter called format set to json.

Note that the success block inserts keys from the response into the empty h1 and p blocks in the calling view. (You may have been wondering about those when you saw the first example. Mystery solved.)

app/controllers/Say.cfc
component extends="Controller" {
    function config() {
        provides("html,json");
    }

    function hello() {
        // Prepare the message for the user.
        greeting = {};
        greeting["message"] = "Hi there";
        greeting["time"] = Now();

        // Respond to all requests with `renderWith()`.
        renderWith(greeting);
    }
}

Lastly, notice the lines where we're setting greeting["message"] and greeting["time"]. Due to the case-insensitive nature of ColdFusion, we recommend setting variables to be consumed by JavaScript using bracket notation like that. If you do not use that notation (i.e., greetings.message and greetings.time instead), your JavaScript will need to reference those keys from the JSON as MESSAGE and TIME (all caps). Unless you like turning caps lock on and off, you can see how that would get annoying after some time.

Assuming you already included jQuery in your application and you followed the code examples above, you now have a simple AJAX-powered web application built on Wheels. After clicking that Alert me! link, your say controller will respond back to you the serialized message via AJAX. jQuery will parse the JSON object and populate the h1 and pwith the appropriate data.

AJAX in Wheels Explained

That is it! Hopefully now you have a clearer picture on how to create AJAX-based features for your web applications.

Requirements

What you need to know and have installed before you start programming in Wheels.

We can identify 3 different types of requirements that you should be aware of:

  • Project Requirements. Is Wheels a good fit for your project?

  • Developer Requirements. Do you have the knowledge and mindset to program effectively in Wheels?

  • System Requirements. Is your server ready for Wheels?

Project Requirements

Before you start learning Wheels and making sure all the necessary software is installed on your computer, you really need to take a moment and think about the project you intend to use Wheels on. Is it a ten page website that won't be updated very often? Is it a space flight simulator program for NASA? Is it something in between?

Most websites are, at their cores, simple data manipulation applications. You fetch a row, make some updates to it, stick it back in the database and so on. This is the "target market" for Wheels--simple CRUD (create, read, update, delete) website applications.

A simple ten page website won't do much data manipulation, so you don't need Wheels for that (or even ColdFusion in some cases). A flight simulator program will do so much more than simple CRUD work, so in that case, Wheels is a poor match for you (and so perhaps, is ColdFusion).

If your website falls somewhere in between these two extreme examples, then read on. If not, go look for another programming language and framework. ;)

Another thing worth noting right off the bat (and one that ties in with the simple CRUD reasoning above) is that Wheels takes a very data-centric approach to the development process. What we mean by that is that it should be possible to visualize and implement the database design early on in the project's life cycle. So, if you're about to embark on a project with an extensive period of object oriented analysis and design which, as a last step almost, looks at how to persist objects, then you should probably also look for another framework.

Still reading?

Good!

Moving on...

Developer Requirements

Yes, there are actually some things you should familiarize yourself with before starting to use Wheels. Don't worry though. You don't need to be an expert on any on of them. A basic understanding is good enough.

  • CFML. You should know CFML, the ColdFusion programming language. (Surprise!)

  • Object Oriented Programming. You should grasp the concept of object oriented programming and how it applies to CFML.

  • Model-View-Controller. You should know the theory behind the Model-View-Controller development pattern.

CFML

Object Oriented Programming (OOP)

This is a programming methodology that uses constructs called objects to design applications. Objects model real world entities in your application. OOP is based on several techniques including inheritance, modularity, polymorphism, and encapsulation. Most of these techniques are supported in CFML, making it a fairly functional object oriented language. At the most basic level, a .cfc file in CFML is a class, and you create an instance of a class by using the CreateObject function or the <cfobject> tag.

Trying to squeeze an explanation of object oriented programming and how it's used in CFML into a few sentences is impossible, and a detailed overview of it is outside the scope of this chapter. There is lots of high quality information online, so go ahead and Google it.

Model-View-Controller

Model-View-Controller, or MVC for short, is a way to structure your code so that it is broken down into 3 easy-to-manage pieces:

  • Model. Just another name for the representation of data, usually a database table.

  • View. What the user sees and interacts with (a web page in our case).

  • Controller. The behind-the-scenes guy that's coordinating everything.

MVC is how Wheels structures your code for you. As you start working with Wheels applications, you'll see that most of the code you write is very nicely separated into one of these 3 categories.

System Requirements

Wheels requires that you use one of these CFML engines:

Operating Systems

Your ColdFusion or Lucee engine can be installed on Windows, Mac, UNIX, or Linux—they all work just fine.

Web Servers

Database Engines

Finally, to build any kind of meaningful website application, you will likely interact with a database. These are the currently supported databases:

  • SQL Server 7+

  • MySQL 5+ *

  • PostgreSQL 8.4+

  • H2 1.4+

MySQL

  • Wheels maybe incompatible with newer MySQL JDBC drivers. It is recommended you downgrade the driver to version 5.1.x for full ORM functionality.

  • MySQL 4 is not supported.

OK, hopefully this chapter didn't scare you too much. You can move on knowing that you have the basic knowledge needed, the software to run Wheels, and a suitable project to start with.

Manual Installation

Instructions for installing Wheels on your system.

Installing Wheels is so simple that there is barely a need for a chapter devoted to it. But we figured we'd better make one anyway in case anyone is specifically looking for a chapter about installation.

So, here are the simple steps you need to follow to get rolling on Wheels...

Manual Installation

1. Download Wheels

You have 2 choices when downloading Wheels. You can either use the latest official release of Wheels, or you can take a walk on the wild side and go with the latest committed source code in our Git repository.

In most cases, we recommend going with the official release because it's well documented and has been through a lot of bug testing. Only if you're in desperate need of a feature that has not been released yet would we advise you to go with the version stored in the Git master branch.

Let's assume you have downloaded the latest official release. (Really, you should go with this option.) You now have a .zip file saved somewhere on your computer. On to the next step...

2. Setup the Website

Getting an empty website running with Wheels installed is an easy process if you already know your way around IIS or Apache. Basically, you need to create a new website in your web server of choice and unzip the contents of the file into the root of it.

  • Create a new folder under your web root (usually C:\Inetpub\wwwroot) named wheels_site and unzip the Wheels .zip file into the root of it.

  • Create a new website using IIS called Wheels Site with localhost as the host header name and C:\Inetpub\wwwroot\mysite as the path to your home directory.

3. Setup the Database (Optional)

Create a new database in MySQL, PostgreSQL, Microsoft SQL Server, or H2 and add a new data source for it in the ColdFusion/Lucee Administrator, just as you'd normally do. Now open up app/config/settings.cfm and call set(dataSourceName="") with the name you chose for the data source.

4. Test It

When you've followed the steps above, you can test your installation by typing http://localhost/ (or whatever you set as the host header name) in your web browser. You should get a "Congratulations!" page.

That's it. You're done. This is where the fun begins!

Screencasts

Tutorials, demonstrations, and presentations about the ColdFusion on Wheels framework.

Wheels 2.x

Wheels 1.x

Please note that all the webcasts below were created with Wheels 1.x in mind, and are listed here as they might still be useful to those starting out.

CRUD series

"Building a Social Network"

Chris Peters adds data validation to the user registration form

Other

Upgrading

Instructions for upgrading Wheels applications

Generally speaking, upgrading Wheels is as easy as replacing the wheels folder, especially for those small maintenance releases: however, there are usually exceptions in minor point releases (i.e, 1.1 to 1.3 required replacing other files outside the wheels folder). The notes below detail those changes.

Upgrading to 3.0.0

Compatibility Changes

Adobe Coldfusion 2016 and below are no longer compatible with Wheels going forward. Consequently, these versions have been removed from the Wheels Internal Test Suites.

Code changes

  • After installing Wheels 3.x, you'll have to run box install to intall testbox and wirebox in your application as they are not shipped with Wheels but are rather listed in box.json file as dependencies to be installed.

  • Added Mappings for the app, vendor, wheels, wirebox, testbox and tests directories.

  • root.cfm and rewrite.cfm have been removed. All the requests are now being redirected only through public/index.cfm.

Changes to the wheels folder

  • Replace the wheels folder with the new one from the 3.0.0 download.

  • Move the wheels folder inside the vendor folder.

Changes outside the wheels folder

  • Moved the config, controllers, events, global, lib, migrator, models, plugins, snippets and views directories inside the app directory.

  • Moved the files, images, javascripts, miscellaneous, stylesheets directories and Application.cfc, index.cfm and urlrewrite.xml files into the public folder.

Upgrading to 2.3.x

Replace the wheels folder with the new one from the 2.3.0 download.

Upgrading to 2.2.x

Replace the wheels folder with the new one from the 2.2.0 download.

Upgrading to 2.1.x

Replace the wheels folder with the new one from the 2.1.0 download.

Code changes

  • Rename any instances of findLast() to findLastOne()

Changes outside the wheels folder

  • Create /events/onabort.cfm to support the onAbort method

Upgrading to 2.0.x

As always, the first step is to replace the wheels folder with the new one from the 2.0 download.

Other major changes required to upgrade your application are listed in the following sections.

Supported CFML Engines

Wheels 2.0 requires one of these CFML engines:

  • Lucee 4.5.5.006 + / 5.2.1.9+

  • Adobe ColdFusion 10.0.23 / 11.0.12+ / 2016.0.4+

We've updated our minimum requirements to match officially supported versions from the vendors. (For example, Adobe discontinued support for ColdFusion 10 in May 2017, which causes it to be exposed to security exploits in the future. We've included it in 2.0 but it may be discontinued in a future version)

Changes outside the wheels folder

  • The events/functions.cfm file has been moved to global/functions.cfm.

  • The models/Model.cfc file should extend wheels.Model instead of Wheels (models/Wheels.cfc can be deleted).

  • The controllers/Controller.cfc file should extend wheels.Controller instead of Wheels(controllers/Wheels.cfc can be deleted).

  • The init function of controllers and models must be renamed to config.

  • The global setting modelRequireInit has been renamed to modelRequireConfig.

  • The global setting cacheControllerInitialization has been renamed to cacheControllerConfig.

  • The global setting cacheModelInitialization has been renamed to cacheModelConfig.

  • The global setting clearServerCache has been renamed to clearTemplateCache.

  • The updateProperties() method has been removed, use update() instead.

  • The renderPage function has been renamed to renderView

  • includePartial() now requires the partial and query arguments to be set (if using a query)

Routing

By default, this is limited to GET requests for security reasons.

Cross-Site Request Forgery (CSRF) Protection

It is strongly recommended that you enable Wheels 2.0's built-in CSRF protection.

For many applications, you need to follow these steps:

  1. Update your route definitions to enforce HTTP verbs on actions that manipulate data (get, post, patch, delete, etc.)

  2. Make sure that forms within the application are POSTing data to the actions that require post, patch, and delete verbs.

Database Migrations

If you have previously been using the dbmigrate plugin, you can now use the inbuilt version within the Wheels 2 core.

Database Migration files in /db/migrate/ should now be moved to /migrator/migrations and extend wheels.migrator.Migration, not plugins.dbmigrate.Migration which can be changed with a simple find and replace. Note: Oracle is not currently supported for Migrator.

Upgrading to 1.4.x

  1. Replace the wheels folder with the new one from the 1.4 download.

  2. Replace URL rewriting rule files – i.e, .htaccess, web.config, IsapiRewrite.ini

In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.

Upgrading to 1.3.x

If you are upgrading from Wheels 1.1.0 or newer, follow these steps:

  1. Replace the wheels folder with the new one from the 1.3 download.

  2. Replace the root root.cfm file with the new one from the 1.3 download.

  3. Remove the <cfheader> calls from the following files:

    • events/onerror.cfm

    • events/onmaintenance.cfm

    • events/onmissingtemplate.cfm

In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.

Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.

Upgrading to 1.1.x

If you are upgrading from Wheels 1.0 or newer, the easiest way to upgrade is to replace the wheels folder with the new one from the 1.1 download. If you are upgrading from an earlier version, we recommend reviewing the steps outlined in Upgrading to Wheels 1.0.

Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.

Plugin Compatibility

Be sure to review your plugins and their compatibility with your newly-updated version of Wheels. Some plugins may stop working, throw errors, or cause unexpected behavior in your application.

Supported System Changes

  • 1.1: The minimum Adobe ColdFusion version required is now 8.0.1.

  • 1.1: The minimum Railo version required is now 3.1.2.020.

  • 1.1: The H2 database engine is now supported.

File System Changes

  • 1.1: The .htaccess file has been changed. Be sure to copy over the new one from the new version 1.1 download and copy any addition changes that you may have also made to the original version.

Database Structure Changes

  • 1.1: By default, Wheels 1.1 will wrap database queries in transactions. This requires that your database engine supports transactions. For MySQL in particular, you can convert your MyISAM tables to InnoDB to be compatible with this new functionality. Otherwise, to turn off automatic transactions, place a call to set(transactionMode="none").

  • 1.1: Binary data types are now supported.

CFML Code Changes

Model Code

  • 1.1: Validations will be applied to some model properties automatically. This may cause unintended behavior with your validations. To turn this setting off, call set(automaticValidations=false) in config/settings.cfm.

  • 1.1: The class argument in hasOne(), hasMany(), and belongsTo() has been deprecated. Use the modelName argument instead.

  • 1.1: afterFind() callbacks no longer require special logic to handle the setting of properties in objects and queries. (The "query way" works for both cases now.) Because arguments will always be passed in to the method, you can't rely on StructIsEmpty() to determine if you're dealing with an object or not. In the rare cases that you need to know, you can now call isInstance() or isClass() instead.

  • 1.1: On create, a model will now set the updatedAt auto-timestamp to the same value as the createdAt timestamp. To override this behavior, call set(setUpdatedAtOnCreate=false) in config/settings.cfm.

View Code

  • 1.1: Object form helpers (e.g. textField() and radioButton()) now automatically display a label based on the property name. If you left the label argument blank while using an earlier version of Wheels, some labels may start appearing automatically, leaving you with unintended results. To stop a label from appearing, use label=false instead.

  • 1.1: The contentForLayout() helper to be used in your layout files has been deprecated. Use the includeContent() helper instead.

  • 1.1: In production mode, query strings will automatically be added to the end of all asset URLs (which includes JavaScript includes, stylesheet links, and images). To turn off this setting, call set(assetQueryString=false) in config/settings.cfm.

  • 1.1: stylesheetLinkTag() and javaScriptIncludeTag() now accept external URLs for the source/sources argument. If you manually typed out these tags in previous releases, you can now use these helpers instead.

  • 1.1: flashMessages(), errorMessageOn(), and errorMessagesFor() now create camelCased class attributes instead (for example error-messages is now errorMessages). The same goes for the class attribute on the tag that wraps form elements with errors: it is now fieldWithErrors.

Controller Code

  • 1.1.1: The if argument in all validation functions is now deprecated. Use the condition argument instead.

Upgrading to 1.0.x

Our listing of steps to take while upgrading your Wheels application from earlier versions to 1.0.x.

Upgrading from an earlier version of 1.x? Then the upgrade path is simple. All you need to do is replace the wheels folder with the new wheels folder from the download.

The easiest way to upgrade is to setup an empty website, deploy a fresh copy of Wheels 1.0, and then transfer your application code to it. When transferring, please make note of the following changes and make the appropriate changes to your code.

Note: To accompany the newest 1.0 release, we've highlighted the changes that are affected by that release.

Supported System Changes

  • 1.0: URL rewriting with IIS 7 is now supported.

  • 1.0: URL rewriting in a sub folder on Apache is now supported.

  • ColdFusion 9 is now supported.

  • Oracle 10g or later is now supported.

  • PostgreSQL is now supported.

  • Railo 3.1 is now supported.

File System Changes

  • 1.0: There is now an app.cfm file in the config folder. Use it to set variables that you'd normally set in Application.cfc (i.e., this.name, this.sessionManagement, this.customTagPaths, etc.)

  • 1.0: There is now a web.config file in the root.

  • 1.0: There is now a Wheels.cfc file in the models folder.

  • 1.0: The Wheels.cfc file in the controllers folder has been updated.

  • 1.0: The IsapiRewrite4.ini and .htaccess files in the root have both been updated.

  • The controller folder has been changed to controllers.

  • The model folder has been changed to models.

  • The view folder has been changed to views.

  • Rename all of your CFCs in models and controllers to UpperCamelCase. So controller.cfc will become Controller.cfc, adminUser.cfc will become AdminUser.cfc, and so on.

  • All images must now be stored in the images folder, files in the files folder, JavaScript files in the javascripts folder, and CSS files in the stylesheets folder off of the root.

Database Structure Changes

  • deletedOn, updatedOn, and createdOn are no longer available as auto-generated fields. Please change the names to deletedAt, updatedAt, and createdAt instead to get similar functionality, and make sure that they are of type datetime, timestamp, or equivalent.

CFML Code Changes

Config Code

  • 1.0: The action of the default route (home) has changed to wheels. The way configuration settings are done has changed quite a bit. To change a Wheels application setting, use the new set() function with the name of the Wheels property to change. (For example, <cfset set(dataSourceName="mydatasource")>.) To see a list of available Wheels settings, refer to the Configuration and Defaults chapter. Model Code

  • 1.0: The extends attribute in models/Model.cfc should now be Wheels.

  • findById() is now called findByKey(). Additionally, its id argument is now named key instead. For composite keys, this argument will accept a comma-delimited list.

  • When using a model's findByKey() or findOne() functions, the found property is no longer available. Instead, the functions return false if the record was not found.

  • A model's errorsOn() function now always returns an array, even if there are no errors on the field. When there are errors for the field, the array elements will contain a struct with name, fieldName, and message elements.

  • The way callbacks are created has changed. There is now a method for each callback event ( beforeValidation(), beforeValidationOnCreate(), etc.) that should be called from your model's init() method. These methods take a single argument: the method within your model that should be invoked during the callback event. See the chapter on Object Callbacks for an example.

View Code

  • 1.0: The contents of the views/wheels folder have been changed.

  • The wrapLabel argument in form helpers is now replaced with labelPlacement. Valid values for labelPlacement are before, after, and around.

  • The first argument for includePartial() has changed from name to partial. If you're referring to it through a named argument, you'll need to replace all instances with partial.

  • The variable that keeps a counter of the current record when using includePartial() with a query has been renamed from currentRow to current.

  • There is now an included wheels view folder in views. Be sure to copy that into your existing Wheels application if you're upgrading.

  • The location of the default layout has changed. It is now stored at /views/layout.cfm. Now controller-specific layouts are stored in their respective view folder as layout.cfm. For example, a custom layout for www.domain.com/about would be stored at /views/about/layout.cfm.

  • In linkTo(), the id argument is now called key. It now accepts a comma-delimited list in the case of composite keys.

  • The linkTo() function also accepts an object for the key argument, in which case it will automatically extract the keys from it for use in the hyperlink.

  • The linkTo() function can be used only for controller-, action-, and route-driven links now. * The url argument has been removed, so now all static links should be coded using a standard "a" tag.

Controller Code

URL/Routing

  • The default route for Wheels has changed from [controller]/[action]/[id] to [controller]/[action]/[key]. This is to support composite keys. The params.id value will now only be available as params.key.

  • You can now pass along composite keys in the URL. Delimit multiple keys with a comma. (If you want to use this feature, then you can't have a comma in the key value itself).

Let's make sure we're all on the same page. I'm going to assume that you've followed the guide and have CommandBox all setup. If you haven't done that, stop and read that guide get everything setup. It's okay, this web page will wait for you.

So what happens if we try to call our new controller right now? Lets take a look. Open your browser and point your browser to the new controller. Because my local server is installed on port 60000, my URL is http://127.0.0.1:60000/say. You may need to enter a different URL, depending on how your web server is configured. In my case, I'm using .

Figure 2: Wheels error after setting up your blank say controller
Figure 3: Your first working Wheels action.
Figure 4: Hello World with the current date and time
Figure 5: Your new goodbye action

The function is a built-in Wheels function. In this case, we are passing 2 named parameters to it. The first parameter, text, is the text that will be displayed in the hyperlink. The second parameter, action, defines the action to point the link to. By using this built-in function, your application's main URL may change, and even controllers and actions may get shifted around, but you won't suffer from the dreaded dead link. Wheels will always create a valid link for you as long as you configure it correctly when you make infrastructure changes to your application.

Figure 6: Your say/hello action with a link to the goodbye action
Figure 7: Your say/goodbye action with a link to the hello action

By far the quickest way to get started with Wheels is via . CommandBox brings a whole host of command line capabilities to the CFML developer. It allows you to write scripts that can be executed at the command line written entirely in CFML. It allows you to start a CFML server from any directory on your machine and wire up the code in that directory as the web root of the server. What's more is, those servers can be either Lucee servers or Adobe ColdFusion servers. You can even specify what version of each server to launch. Lastly, CommandBox is a package manager for CFML. That means you can take some CFML code and package it up into a module, host it on ForgeBox.io, and make it available to other CFML developers. In fact we make extensive use of these capabilities to distribute Wheels plugins and templates. More on that later.

The first step is to get downloaded and running. CommandBox is available for Windows, Mac & Linux, and can be installed manually or using one of the respective package managers for each OS. You can use on Windows, on MacOS, or Yum/Apt on Linux depending on your flavor of Linux. Please follow the instructions on how to install CommandBox on your particular operating system. At the end of the installation process you want to make sure the box command is part of your system path so you can call the command from any directory on your system.

You can download all the source code for this sample application from

These are database used by Wheels. This framework strongly encourages that everyone follow convention over configuration. That way everyone is doing things mostly the same way, leading to less maintenance and training headaches down the road.

To generate the form tag's action attribute, the function takes parameters similar to the function that we introduced in the Beginner Tutorial: Hello World tutorial. We can pass in controller, action, key, and other route- and parameter-defined URLs just like we do with .

To end the form, we use the function. Easy enough.

The and helpers are similar. As you probably guessed, they create <input> elements with type="text" and type="password", respectively. And the function creates an <input type="submit" /> element.

One thing you'll notice is the and functions accept arguments called objectName and property. As it turns out, this particular view code will throw an error because these functions are expecting an object named user. Let's fix that.

As it turns out, our controller needs to provide the view with a blank user object (whose instance variable will also be called user in this case). In our new action, we will use the function to generate a new instance of the user model.

To get a blank set of properties in the model, we'll also call the generated model's method.

A basic way of doing this is using the model object's method:

Notice that our create action above redirects the user to the users index route using the function. We'll use this action to list all users in the system with "Edit" links. We'll also provide a link to the "New User" form that we just coded.

This call to the model's method will return a query object of all users in the system. By using the method's order argument, we're also telling the database to order the records by username.

To update the user, simply call its method with the user struct passed from the form via params. It's that simple.

After the update, we'll add a success message and send the end user back to the edit form in case they want to make more changes.

We simply load the user using the model's method and then call the object's method. That's all there is to it.

You can also specify hosts other than localhost: there's a useful CommandBox module to do that () which will automatically create entries in your hosts file to allow for domains such as myapp.local running on port 80. You can install it via install commandbox-hostupdater when running the box shell with administrator privileges.

While there are several flavors of JavaScript libraries out there with AJAX support, we will be using the in this tutorial. Let's assume that you are fairly familiar with the basics of jQuery and know how to set it up.

But let's make it into an asynchronous request. Add this JavaScript (either on the page inside script tags or in a separate .js file included via ):

The last thing that we need to do is implement the say/hello action. Note that the request expects a dataType of JSON. By default, Wheels controllers only generate HTML responses, but there is an easy way to generate JSON instead using Wheels's and functions:

In this controller's config() method, we use the function to indicate that we want all actions in the controller to be able to respond with the data in HTML or JSON formats. Note that the client calling the action can request the type by passing a URL parameter named format or by sending the format in the request header.

The call to in the hello action takes care of the translation to the requested format. Our JavaScript is requesting JSON, so Wheels will format the greeting struct as JSON automatically and send it back to the client. If the client requested HTML or the default of none, Wheels will process and serve the view template at app/views/say/hello.cfm. For more information about and , reference the chapter on .

Simply the best web development language in the world! The best way to learn it, in our humble opinion, is to get the free developer edition of Adobe ColdFusion, buy Ben Forta's ColdFusion Web Application Construction Kit series, and start coding using your programming editor of choice. Remember it's not just the commercial Adobe offering that's available; offers an excellent open source alternative. Using is a great and simple way to get a local development environment of your choice up and running quickly.

2018 / 2021 / 2023

5.2.1.9+ / 6

You also need a web server. Wheels runs on all popular web servers, including Apache, Microsoft IIS, Jetty, and the JRun or Tomcat web server that ships with Adobe ColdFusion. Some web servers support URL rewriting out of the box, some support the cgi.PATH_INFO variable which is used to achieve partial rewriting, and some don't have support for either. For local development, we strongly encourage the use of .

Don't worry though. Wheels will adopt to your setup and run just fine, but the URLs that it creates might differ a bit. You can read more about this in the chapter.

If you're using MySQL 5.7.5+ you should be aware that the ONLY_FULL_GROUP_BY setting is enabled by default and it's currently not compatible with the Wheels ORM. However, you can work around this by either disabling the ONLY_FULL_GROUP_BY setting or using ANY_VALUE() in a calculated property. You can read more about it .

We also recommend using the InnoDB engine if you want to work.

The latest official releases can always be found in the section of GitHub, and the Git repository is available at our .

In case you're not sure, here are the instructions for setting up an empty Wheels site that can be accessed when typing localhost in your browser. The instructions refer to a system running Windows Server 2003 and IIS, but you should be able to follow along and apply the instructions with minor modifications to your system. (See for a list of tested systems).

If you want to run a Wheels-powered application from a subfolder in an existing website, this is entirely possible, but you may need to get a little creative with your URL rewrite rules if you want to get pretty URLs--it will only work out of the box on recent versions of Apache. (Read more about this in the chapter.)

If you don't want to be bothered by opening up a Wheels configuration file at all, there is a nice convention you can follow for the naming. Just name your data source with the same name as the folder you are running your website from (mysite in the example above), and Wheels will use that when you haven't set the dataSourceName setting using the function.

Create a basic CRUD interface in Wheels 2.x

Create a basic JSON API in Wheels 2.x

Routing in Wheels 2.x - Part 1

Routing in Wheels 2.x - Part 2

Introduction to Unit Testing in Wheels 2.x

Unit Testing Controllers in Wheels 2.x

Learn about basic create operations when building standard CRUD functionality in Wheels

Learn about basic read operations when building standard CRUD functionality in Wheels

Chris Peters demonstrates updating data in a simple CRUD Wheels application

Learn how simple it is to delete records in a basic CRUD application using Wheels

Chris Peters starts the webcast series by demonstrating how to set up ColdFusion on Wheels

Chris Peters demonstrates how to bind a Wheels model object to a form through the use of form helpers

Chris Peters finishes the "success" portion of the registration functionality by adding a success message to the Flash and redirecting the user to their home screen

Chris Peters teaches you about more validation options and how you can add them to the registration form quickly and easily

Chris Peters stylizes form markup globally using a Wheels feature called global helpers

Learn how to set up simple user authentication on a website by using a Wheels feature called filters

Learn the mechanics of reading a single record from the database and displaying its data in the view

Creating custom URL patterns is a breeze in ColdFusion on Wheels

Learn how to fetch multiple records from your model with findAll() and then display them to the user using ColdFusion on Wheels

Learn how to factor out logic in your view templates into custom helper functions in ColdFusion on Wheels

Chris Peters demonstrates joining data together with model associations using ColdFusion on Wheels

All it takes to offer pagination is two extra arguments to findAll() and a call to a view helper called paginationLinks()

Learn how to use the provides() and renderWith() functions to automatically serialize data into XML, JSON, and more

Peter Amiri walks you through setting up a "Hello World" application using the ColdFusion on Wheels framework

Chris Peters gives a high level overview of the ORM included with ColdFusion on Wheels

Chris Peters from Liquifusion demonstrates the ColdRoute plugin for Wheels

Doug Boude demonstrates using his new Wirebox plugin for Wheels

Chris Peters from Liquifusion demonstrates creating tables and records using database migrations in ColdFusion on Wheels

Online ColdFusion Meetup (coldfusionmeetup.com) session for March 10 2011, "What's New in Wheels 1.1", with Chris Peters:

A quick demo of the Wheels Textmate bundle by Russ Johnson

Wheels follows Semantic Versioning () so large version changes (e.g, 1.x.x -> 2.x.x) will most likely contain breaking changes which will require evaluation of your codebase. Minor version changes (e.g, 1.3.x->1.4.x) will often contain new functionality, but in a backwards-compatible manner, and maintenance releases (e.g 1.4.4 -> 1.4.5) will just be trying to fix bugs.

Migrate your tests from the tests directory which are written with rocketUnit and rewrite them into in the tests/Testbox directory. Starting with Wheels 3.x, will replace RocketUnit as the default testing framework.

Starting with Wheels 3.x, will be used as the default dependency injector.

A .env file has been added in the root of the application which adds the H2 database extension for lucee and sets the cfadmin password to commandbox for both and .

JavaScript arguments like confirm and disable have been removed from the link and form helper functions (use the and plugins to reinstate the old behavior).

The function has been removed in Wheels 2.0 in favor of a new routing API. See the chapter for information about the new RESTful routing system.

A limited version of the "wildcard" route ([controller]/[action]/[key]) is available as [controller]/[action]) if you use the new mapper method:

In controllers/Controller.cfc, add a call to to the config method.

Add a call to the helper in your layouts' <head> sections.

Configure any AJAX calls that POST data to your application to pass the authenticityToken from the <meta>tags generated by as an X-CSRF-TOKEN HTTP header.

See documentation for the for more information.

Note: If you had previously installed the , you may remove it and rely on the functionality included in the Wheels 2 core.

1.0: The extends attribute in controllers/Controller.cfc should now be Wheels. Multiple-word controllers and actions are now word-delimited by hyphens in the URL. For example, if your controller is called SiteAdmin and the action is called editLayout, the URL to access that action would be .

Getting Started
CommandBox
linkTo()
CommandBox
CommandBox
Chocolatey
Homebrew
https://github.com/dhgassoc/Wheels-Beginner-Tutorial-Hello-Database
conventions
startFormTag()
linkTo()
linkTo()
endFormTag()
textField()
passwordField()
submitTag()
textField()
passwordField()
model()
new()
create()
redirectTo()
findAll()
update()
using the Flash
findByKey()
delete()
Host updater
jQuery framework
javaScriptIncludeTag()
provides()
renderWith()
provides()
renderWith()
provides()
renderWith()
Responding with Multiple Formats
Lucee
CommandBox
Adobe ColdFusion
Lucee
CommandBox
URL Rewriting
here
Transactions
app/config/routes.cfm
mapper()
    .wildcard()
.end();
Releases
GitHub repo
Requirements
URL Rewriting
Set()
https://youtu.be/K5HLItTru1g
https://youtu.be/qZr5JzO0vo4
https://youtu.be/BnPGApAvMVQ
https://youtu.be/0CiGxJyJEIQ
https://youtu.be/XgMuzzmBQ98
https://youtu.be/cygj9WDqHjY
View all screencasts on Vimeo
Episode 1: "C" Is for "Create" - Basic CRUD
Episode 2: "R"; Is for "Read" - Basic CRUD
Episode 3: "U" Is for "Update" - Basic CRUD
Episode 4: "D" Is for Delete - Basic CRUD
Episode 1: Setting up ColdFusion on Wheels
Episode 2: Form Helpers
Episode 3: Object Validation and Showing Errors
Episode 4: Redirects and the Flash
Episode 5: Object Validation
Episode 6: Styling Forms
Episode 7: Authentication with Filters
Episode 8: Reading and Displaying a Single Record
Episode 9: Adding a Route for User Profiles
Episode 10: Displaying Sets of Records
Episode 11: Custom View Helpers
Episode 12: Joining Models with Associations
Episode 13: Pagination
Episode 14: Responding with Multiple Formats
Hello World
CFUnited 2010: Simplifying Database Code with the ColdFusion on Wheels ORM
ColdRoute Plugin
Wirebox Plugin for Wheels
Database Migrations
CF Meetup, March 10 2011
Wheels Textmate Bundle Demo
http://semver.org/
Testbox
Testbox
Wirebox
Lucee
Adobe ColdFusion
JS Confirm
JS Disable
addRoute()
Routing
wildcard()
protectsFromForgery()
csrfMetaTags()
csrfMetaTags()
CSRF Protection Plugin
CSRF Protection plugin
http://www.domain.com/site-admin/edit-layout

CLI Overview

Welcome to the comprehensive documentation for the Wheels CLI - a powerful command-line interface for the Wheels framework.

What is Wheels CLI?

Wheels CLI is a CommandBox module that provides a comprehensive set of tools for developing Wheels applications. It offers:

  • Code Generation - Quickly scaffold models, controllers, views, and complete CRUD operations

  • Database Migrations - Manage database schema changes with version control

  • Testing Tools - Run tests, generate coverage reports, and use watch mode

  • Development Tools - File watching, automatic reloading, and development servers

  • Code Analysis - Security scanning, performance analysis, and code quality checks

  • Plugin Management - Install and manage Wheels plugins

  • Environment Management - Switch between development, testing, and production

Documentation Structure

Complete reference for all CLI commands organized by category:

  • Core Commands - Essential commands like init, reload, watch

Get up and running with Wheels CLI in minutes. Learn how to:

  • Install Wheels CLI

  • Create your first application

  • Generate CRUD scaffolding

  • Run tests and migrations

📖 Guides

Development Guides

Best Practices

📋 Reference

Key Features

🛠️ Code Generation

Generate complete applications or individual components:

# Create new application
wheels new blog

# Generate complete CRUD scaffolding
wheels scaffold post --properties="title:string,content:text,published:boolean"

# Generate individual components
wheels generate model user
wheels generate controller users --rest
wheels generate view users index

🗄️ Database Migrations

Manage database schema changes:

# Create migration
wheels dbmigrate create table posts

# Run migrations
wheels dbmigrate latest

# Check status
wheels dbmigrate info

🧪 Testing

Comprehensive testing support:

# Run all tests
wheels test run

# Watch mode
wheels test run --watch

# Generate coverage
wheels test coverage

👀 Development Tools

Enhance your development workflow:

# Watch for file changes
wheels watch

# Reload application
wheels reload development

# Analyze code
wheels analyze code
wheels security scan

Getting Started

  1. Install CommandBox (if not already installed):

    # macOS/Linux
    curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
    or
      brew install commandbox
    
    # Windows
    choco install commandbox
  2. Install Wheels CLI:

    box install wheels-cli
  3. Create Your First App:

    wheels new myapp
    cd myapp
    box server start
  4. Explore Commands:

    wheels --help
    wheels generate --help
    wheels dbmigrate --help

Version Compatibility

Wheels CLI
Wheels
CommandBox
CFML Engine

3.0.x

2.5+

5.0+

Lucee 5.3+, Adobe 2018+

2.0.x

2.0-2.4

4.0+

Lucee 5.2+, Adobe 2016+

Community & Support

Contributing

  • Reporting issues

  • Suggesting features

  • Submitting pull requests

  • Creating custom commands

Recent Updates

Version 3.0.0

  • 🆕 Modernized service architecture

  • 🆕 Enhanced testing capabilities with watch mode

  • 🆕 Security scanning and performance optimization

  • 🆕 Plugin and environment management

  • 🆕 Improved code generation with more options

  • 🔧 Better error handling and user feedback

  • 📚 Comprehensive documentation

Quick Links

License


Command Reference

Complete reference for all Wheels CLI commands organized by category.

Quick Reference

Most Common Commands

Command
Description

wheels generate app [name]

Create new application

wheels scaffold [name]

Generate complete CRUD

wheels dbmigrate latest

Run database migrations

wheels test run

Run application tests

wheels watch

Watch files for changes

wheels reload

Reload application

Core Commands

Essential commands for managing your Wheels application.

Command
Description
Documentation

wheels init

Bootstrap existing app for CLI

wheels info

Display version information

wheels reload [mode]

Reload application

wheels deps

Manage dependencies

wheels destroy [type] [name]

Remove generated code

wheels watch

Watch for file changes

Code Generation

Commands for generating application code and resources.

Command
Alias
Description
Documentation

wheels generate app

wheels new

Create new application

wheels generate app-wizard

Interactive app creation

wheels generate controller

wheels g controller

Generate controller

wheels generate model

wheels g model

Generate model

wheels generate view

wheels g view

Generate view

wheels generate property

Add model property

wheels generate route

Generate route

wheels generate resource

REST resource

wheels generate api-resource

API resource (Currently broken)

wheels generate frontend

Frontend code

wheels generate test

Generate tests

wheels generate snippets

Code snippets

wheels scaffold

Complete CRUD

Generator Options

Common options across generators:

  • --force - Overwrite existing files

  • --help - Show command help

Database Commands

Commands for managing database schema and migrations.

Migration Management

Command
Description
Documentation

wheels dbmigrate info

Show migration status

wheels dbmigrate latest

Run all pending migrations

wheels dbmigrate up

Run next migration

wheels dbmigrate down

Rollback last migration

wheels dbmigrate reset

Reset all migrations

wheels dbmigrate exec [version]

Run specific migration

Migration Creation

Command
Description
Documentation

wheels dbmigrate create blank [name]

Create empty migration

wheels dbmigrate create table [name]

Create table migration

wheels dbmigrate create column [table] [column]

Add column migration

wheels dbmigrate remove table [name]

Drop table migration

Database Operations

Command
Description
Documentation

wheels db schema

Export/import schema

wheels db seed

Seed database

Testing Commands

Commands for running and managing tests.

Command
Description
Documentation

wheels test [type]

Run framework tests

wheels test run [spec]

Run TestBox tests

wheels test coverage

Generate coverage report

wheels test debug

Debug test execution

Test Options

  • --watch - Auto-run on changes

  • --reporter - Output format (simple, json, junit)

  • --bundles - Specific test bundles

  • --labels - Filter by labels

Configuration Commands

Commands for managing application configuration.

Command
Description
Documentation

wheels config list

List configuration

wheels config set [key] [value]

Set configuration

wheels config env

Environment config

Environment Management

Commands for managing development environments.

Command
Description
Documentation

wheels env

Environment management base command

wheels env setup [name]

Setup environment

wheels env list

List environments

wheels env switch [name]

Switch environment

Plugin Management

Commands for managing Wheels plugins.

Command
Description
Documentation

wheels plugins

Plugin management base command

wheels plugins list

List plugins

wheels plugins install [name]

Install plugin

wheels plugins remove [name]

Remove plugin

Plugin Options

  • --global - Install/list globally

  • --dev - Development dependency

Code Analysis

Commands for analyzing code quality and patterns.

Command
Description
Documentation

wheels analyze

Code analysis base command

wheels analyze code

Analyze code quality

wheels analyze performance

Performance analysis

wheels analyze security

Security analysis (deprecated)

Security Commands

Commands for security scanning and hardening.

Command
Description
Documentation

wheels security

Security management base command

wheels security scan

Scan for vulnerabilities

Security Options

  • --fix - Auto-fix issues

  • --path - Specific path to scan

Performance Commands

Commands for optimizing application performance.

Command
Description
Documentation

wheels optimize

Optimization base command

wheels optimize performance

Optimize application

Documentation Commands

Commands for generating and serving documentation.

Command
Description
Documentation

wheels docs

Documentation base command (Currently broken)

wheels docs generate

Generate documentation

wheels docs serve

Serve documentation

Documentation Options

  • --format - Output format (html, markdown)

  • --output - Output directory

  • --port - Server port

CI/CD Commands

Commands for continuous integration and deployment workflows.

Command
Description
Documentation

wheels ci init

Initialize CI/CD configuration

Docker Commands

Commands for Docker container management and deployment.

Command
Description
Documentation

wheels docker init

Initialize Docker configuration

wheels docker deploy

Deploy using Docker

Deployment Commands

Commands for managing application deployments.

Command
Description
Documentation

wheels deploy

Deployment base command

wheels deploy audit

Audit deployment configuration

wheels deploy exec

Execute deployment

wheels deploy hooks

Manage deployment hooks

wheels deploy init

Initialize deployment

wheels deploy lock

Lock deployment state

wheels deploy logs

View deployment logs

wheels deploy proxy

Configure deployment proxy

wheels deploy push

Push deployment

wheels deploy rollback

Rollback deployment

wheels deploy secrets

Manage deployment secrets

wheels deploy setup

Setup deployment environment

wheels deploy status

Check deployment status

wheels deploy stop

Stop deployment

Deployment Options

  • --environment - Target environment

  • --force - Force deployment

  • --dry-run - Preview changes without deploying

Command Patterns

Getting Help

Every command supports --help:

wheels [command] --help
wheels generate controller --help
wheels dbmigrate create table --help

Command Aliases

Many commands have shorter aliases:

wheels g controller users  # Same as: wheels generate controller users
wheels g model user       # Same as: wheels generate model user
wheels new myapp         # Same as: wheels generate app myapp

Common Workflows

Creating a new feature:

wheels scaffold product --properties="name:string,price:decimal"
wheels dbmigrate latest
wheels test run

Starting development:

wheels watch              # Terminal 1
box server start         # Terminal 2
wheels test run --watch  # Terminal 3

Deployment preparation:

wheels test run
wheels security scan
wheels optimize performance
wheels dbmigrate info

Environment Variables

Variable
Description
Default

WHEELS_ENV

Environment mode

development

WHEELS_DATASOURCE

Database name

From config

WHEELS_RELOAD_PASSWORD

Reload password

From config

Exit Codes

Code
Description

0

Success

1

General error

2

Invalid arguments

3

File not found

4

Permission denied

5

Database error

Command Status Notes

Some commands in the Wheels CLI are currently in various states of development or maintenance:

Broken Commands

  • wheels docs - Base documentation command is currently broken

  • wheels generate api-resource - API resource generation is currently broken

Disabled Commands

The following commands exist in the codebase but are currently disabled:

  • Some CI and Docker commands have disabled variants in the codebase

These commands may be re-enabled in future versions of Wheels.

See Also

wheels reload

Reload the Wheels application in different modes.

Synopsis

wheels reload [mode] [password]

Description

The wheels reload command reloads your Wheels application, clearing caches and reinitializing the framework. This is useful during development when you've made changes to configuration, routes, or framework settings.

Arguments

Argument
Description
Default

mode

Reload mode: development, testing, maintenance, production

development

password

Reload password (overrides configured password)

From .wheels-cli.json

Options

Option
Description

--help

Show help information

Reload Modes

Development Mode

wheels reload development
  • Enables debugging

  • Shows detailed error messages

  • Disables caching

  • Ideal for active development

Testing Mode

wheels reload testing
  • Optimized for running tests

  • Consistent environment

  • Predictable caching

Maintenance Mode

wheels reload maintenance
  • Shows maintenance page to users

  • Allows admin access

  • Useful for deployments

Production Mode

wheels reload production
  • Full caching enabled

  • Minimal error information

  • Optimized performance

Examples

Basic reload (development mode)

wheels reload

Reload in production mode

wheels reload production

Reload with custom password

wheels reload development mySecretPassword

Reload for testing

wheels reload testing

Security

  • The reload password must match the one configured in your Wheels application

  • Default password from .wheels-cli.json is used if not specified

  • Password is sent securely to the application

Configuration

Set the default reload password in .wheels-cli.json:

{
  "reload": "mySecretPassword"
}

Or in your Wheels settings.cfm:

set(reloadPassword="mySecretPassword");

Notes

  • Reload clears all application caches

  • Session data may be lost during reload

  • Database connections are refreshed

  • All singletons are recreated

Common Issues

  • Invalid password: Check password in settings

  • Timeout: Large applications may take time to reload

  • Memory issues: Monitor JVM heap during reload

See Also

wheels init

Bootstrap an existing Wheels application for CLI usage.

Synopsis

Description

The wheels init command initializes an existing Wheels application to work with the Wheels CLI. It sets up the necessary configuration files and creates a .wheels-cli.json file in your project root.

Arguments

Options

Examples

Initialize current directory

Initialize with custom name

Initialize specific directory

Initialize with custom reload password

Initialize without creating folders

What It Does

  1. Creates .wheels-cli.json configuration file

  2. Sets up application name and version

  3. Configures reload password

  4. Optionally creates standard Wheels directory structure:

    • /config

    • /controllers

    • /models

    • /views

    • /tests

    • /plugins

Configuration File

The generated .wheels-cli.json contains:

Notes

  • Run this command in the root directory of your Wheels application

  • The reload password is used for the wheels reload command

  • If folders already exist, they won't be overwritten

See Also

wheels info

Display CLI and Wheels framework version information.

Synopsis

Description

The wheels info command displays detailed information about your Wheels CLI installation, the current Wheels application, and the environment.

Options

Output

The command displays:

  1. CLI Information

    • Wheels CLI version

    • CommandBox version

    • Installation path

  2. Application Information (if in a Wheels app directory)

    • Application name

    • Wheels framework version

    • Reload password status

    • Configuration file location

  3. Environment Information

    • CFML engine and version

    • Operating system

    • Java version

Example Output

Use Cases

  • Verify CLI installation

  • Check Wheels framework version

  • Troubleshoot environment issues

  • Confirm application configuration

Notes

  • Run from within a Wheels application directory for full information

  • Application information only appears when .wheels-cli.json exists

  • Sensitive information like passwords are masked

See Also

wheels destroy

Remove generated code and files.

Synopsis

Description

The wheels destroy command reverses the actions of generator commands, removing files and code that were previously generated. It's useful for cleaning up mistakes or removing features.

Arguments

Resource Types

  • controller - Remove a controller and its views

  • model - Remove a model

  • view - Remove a specific view

  • scaffold - Remove entire scaffolding

  • migration - Remove a migration file

  • test - Remove test files

Options

Examples

Remove a controller

Removes:

  • /controllers/Users.cfc

  • /views/users/ directory and all views

Remove a model

Removes:

  • /models/User.cfc

  • Related test files

Remove scaffolding

Removes:

  • /models/Product.cfc

  • /controllers/Products.cfc

  • /views/products/ directory

  • All CRUD views

  • Test files

Remove a specific view

Removes:

  • /views/users/edit.cfm

Remove a migration

Removes:

  • Migration file from /db/migrate/

Dry run to preview

Shows what would be removed without deleting

Force removal without confirmation

Confirmation

By default, the command asks for confirmation:

Safety Features

  1. Confirmation Required: Always asks unless --force is used

  2. Dry Run Mode: Preview changes with --dry-run

  3. No Database Changes: Only removes files, not database tables

  4. Git Awareness: Warns if files have uncommitted changes

What's NOT Removed

  • Database tables or columns

  • Routes (must be manually removed)

  • References in other files

  • Git history

Best Practices

  1. Always use --dry-run first

  2. Commit changes before destroying

  3. Check for file dependencies

  4. Update routes manually

  5. Remove database tables separately

Common Workflows

Undo a scaffold

Clean up a mistake

Notes

  • Cannot be undone - files are permanently deleted

  • Does not remove custom code added to generated files

  • Works only with files created by generators

See Also

wheels deps

Manage application dependencies.

Synopsis

Description

The wheels deps command helps manage your Wheels application's dependencies, including CFML modules, Wheels plugins, and JavaScript packages.

Options

Features

  1. Dependency Analysis

    • Scans box.json for CFML dependencies

    • Checks package.json for Node.js dependencies

    • Identifies Wheels plugins

  2. Version Checking

    • Compares installed vs required versions

    • Identifies outdated packages

    • Shows available updates

  3. Dependency Installation

    • Installs missing dependencies

    • Updates outdated packages

    • Resolves version conflicts

Output Example

Dependency Sources

CFML Dependencies (box.json)

Wheels Plugins

Located in /plugins directory or installed via ForgeBox.

Node Dependencies (package.json)

Commands Executed

Behind the scenes, wheels deps runs:

Use Cases

  • Check dependency status before deployment

  • Ensure all team members have same dependencies

  • Update dependencies safely

  • Troubleshoot missing functionality

Best Practices

  1. Run after pulling new code

  2. Check before deployments

  3. Update dependencies incrementally

  4. Test after updates

Notes

  • Requires box.json for CFML dependencies

  • Optional package.json for Node dependencies

  • Some plugins may require manual configuration

See Also

wheels watch

Watch for file changes and automatically reload the application.

Synopsis

Description

The wheels watch command monitors your application files for changes and automatically triggers actions like reloading the application or running tests. This provides a smooth development workflow with instant feedback.

Arguments

Options

Examples

Basic file watching

Watches current directory for .cfc, .cfm, and .json changes

Watch specific directory

Watch additional file types

Exclude directories

Auto-run tests

Custom debounce timing

Default Behavior

When a file change is detected:

  1. CFC Files (models, controllers)

    • Triggers application reload

    • Clears relevant caches

    • Runs tests if --test enabled

  2. CFM Files (views)

    • Clears view cache

    • No full reload needed

  3. Config Files (.json, settings.cfm)

    • Full application reload

    • Re-reads configuration

Output Example

Advanced Configuration

Create .wheels-watch.json for project-specific settings:

Performance Considerations

  • Large directories may slow down watching

  • Use --exclude to skip unnecessary paths

  • Increase --debounce for grouped changes

  • Consider watching specific subdirectories

Integration with Editors

VS Code

Add to .vscode/tasks.json:

Sublime Text

Create build system:

Common Patterns

Development Workflow

Frontend + Backend

Test-Driven Development

Troubleshooting

  • Too many file descriptors: Increase system limits or exclude more directories

  • Changes not detected: Check file extensions and excluded paths

  • Slow response: Increase debounce time or watch specific directories

  • Tests failing: Ensure test environment is properly configured

Notes

  • Requires file system events support

  • Some network drives may not support watching

  • Symbolic links are followed by default

See Also

wheels generate app-wizard

Interactive wizard for creating a new Wheels application.

Synopsis

Description

The wheels generate app-wizard command provides an interactive, step-by-step wizard for creating a new Wheels application. It guides you through all configuration options with helpful prompts and explanations, making it ideal for beginners or when you want to explore all available options.

Options

Interactive Process

Step 1: Application Name

  • Must be alphanumeric with hyphens/underscores

  • Used for directory and configuration names

Step 2: Template Selection

Step 3: Target Directory

  • Defaults to ./{app-name}

  • Can specify absolute or relative path

Step 4: Database Configuration

Step 5: Additional Features

Step 6: CFML Engine

Step 7: Security Settings

Step 8: Review & Confirm

Wizard Flow

Expert Mode

Enable expert mode for additional options:

Additional prompts in expert mode:

  • Custom server ports

  • JVM settings

  • Environment-specific configurations

  • Advanced routing options

  • Custom plugin repositories

  • Build tool integration

Configuration Profiles

Save and reuse configurations:

Save Profile

Use Profile

List Profiles

Feature Descriptions

Bootstrap CSS

  • Includes Bootstrap 5.x

  • Responsive grid system

  • Pre-styled components

  • Example layouts

jQuery Library

  • Latest jQuery version

  • AJAX helpers configured

  • Example usage in views

Sample Authentication

  • User model with secure passwords

  • Login/logout controllers

  • Session management

  • Protected routes example

API Documentation

  • OpenAPI/Swagger setup

  • Auto-generated documentation

  • Interactive API explorer

Docker Configuration

  • Multi-stage Dockerfile

  • docker-compose.yml

  • Development & production configs

  • Database containers

Post-Creation Steps

After successful creation, the wizard displays:

Error Handling

The wizard handles common issues:

  • Invalid names: Suggests valid alternatives

  • Existing directories: Offers to overwrite or choose new location

  • Missing dependencies: Provides installation instructions

  • Configuration errors: Allows editing before creation

Validation Rules

Application Name

  • Start with letter

  • Alphanumeric plus - and _

  • No spaces or special characters

  • Not a reserved word

Directory Path

  • Must be writable

  • Cannot be system directory

  • Warns if not empty

Passwords

  • Minimum 6 characters

  • Strength indicator

  • Confirmation required

Customization

Custom Templates

Add templates to ~/.wheels/templates/:

Template Configuration

template.json:

Integration

CI/CD Pipeline

Generate with CI configuration:

Includes:

  • .github/workflows/test.yml

  • Build configuration

  • Deployment scripts

IDE Configuration

Generate with IDE files:

Includes:

  • .vscode/settings.json

  • .vscode/launch.json

  • .editorconfig

Best Practices

  1. Run wizard in empty directory

  2. Choose descriptive application names

  3. Configure database early

  4. Enable security features for production

  5. Save profiles for team consistency

  6. Review all settings before confirming

Common Use Cases

API-Only Application

  • Choose Base@BE template

  • Skip Bootstrap/jQuery

  • Enable API documentation

  • Configure CORS settings

Traditional Web Application

  • Choose Base template

  • Include Bootstrap/jQuery

  • Add sample authentication

  • Configure session management

Microservice

  • Choose Base@BE template

  • Configure Docker

  • Set specific ports

  • Minimal dependencies

Troubleshooting

Wizard Freezes

  • Check terminal compatibility

  • Try --no-interactive mode

  • Check system resources

Installation Fails

  • Verify internet connection

  • Check CommandBox version

  • Try --skip-install and install manually

Configuration Issues

  • Review generated .wheels-cli.json

  • Check server.json settings

  • Verify file permissions

See Also

wheels generate app

Create a new Wheels application from templates.

Synopsis

Description

The wheels generate app command creates a new Wheels application with a complete directory structure, configuration files, and optionally sample code. It supports multiple templates for different starting points.

Arguments

Options

Available Templates

Base (Default)

  • Minimal Wheels application

  • Basic directory structure

  • Essential configuration files

Base@BE (Backend Edition)

  • Backend-focused template

  • No view files

  • API-ready configuration

HelloWorld

  • Simple "Hello World" example

  • One controller and view

  • Great for learning

HelloDynamic

  • Dynamic content example

  • Database interaction

  • Form handling

HelloPages

  • Static pages example

  • Layout system

  • Navigation structure

Examples

Create basic application

Create with custom template

Create in specific directory

Create with Bootstrap

Create with H2 database

Create with all options

Generated Structure

Configuration Files

box.json

server.json

.wheels-cli.json

Database Setup

With H2 (Embedded)

  • No external database needed

  • Perfect for development

  • Auto-configured datasource

With External Database

  1. Create application:

  2. Configure in CommandBox:

Post-Generation Steps

  1. Navigate to directory

  2. Install dependencies

  3. Start server

  4. Open browser

Template Development

Create custom templates in ~/.commandbox/cfml/modules/wheels-cli/templates/apps/:

Best Practices

  1. Use descriptive application names

  2. Choose appropriate template for project type

  3. Set secure reload password for production

  4. Configure datasource before starting

  5. Run tests after generation

Common Issues

  • Directory exists: Use --force or choose different name

  • Template not found: Check available templates with wheels info

  • Datasource errors: Configure database connection

  • Port conflicts: Change port in server.json

See Also

wheels generate controller

Generate a controller with actions and optional views.

Synopsis

Description

The wheels generate controller command creates a new controller CFC file with specified actions and optionally generates corresponding view files. It supports both traditional and RESTful controller patterns.

Arguments

Options

Examples

Basic controller

Creates:

  • /controllers/Products.cfc with index action

  • /views/products/index.cfm

Controller with multiple actions

Creates controller with all CRUD actions and corresponding views.

RESTful controller

Automatically generates all RESTful actions:

  • index - List all products

  • show - Show single product

  • new - New product form

  • create - Create product

  • edit - Edit product form

  • update - Update product

  • delete - Delete product

API controller

Creates:

  • /controllers/api/Products.cfc with JSON responses

  • No view files

Custom actions

Generated Code

Basic Controller

RESTful Controller

API Controller

View Generation

Views are automatically generated for non-API controllers:

index.cfm

Naming Conventions

  • Controller names: PascalCase, typically plural (Products, Users)

  • Action names: camelCase (index, show, createProduct)

  • File locations:

    • Controllers: /controllers/

    • Nested: /controllers/admin/Products.cfc

    • Views: /views/{controller}/

Routes Configuration

Add routes in /config/routes.cfm:

Traditional Routes

RESTful Resources

Nested Resources

Testing

Generate tests alongside controllers:

Best Practices

  1. Use plural names for resource controllers

  2. Keep controllers focused on single resources

  3. Use --rest for standard CRUD operations

  4. Implement proper error handling

  5. Add authentication in init() method

  6. Use filters for common functionality

Common Patterns

Authentication Filter

Pagination

Search

See Also

wheels generate view

Generate view files for controllers.

Synopsis

Description

The wheels generate view command creates view files for specified controller actions. It can generate individual views, sets of views for RESTful actions, or custom view templates with various layout options.

Arguments

Options

Examples

Basic view

Creates: /views/products/index.cfm

Multiple views

Creates multiple view files in /views/products/

RESTful views

Generates all RESTful views:

  • index.cfm - List view

  • show.cfm - Detail view

  • new.cfm - Create form

  • edit.cfm - Edit form

  • _form.cfm - Shared form partial

Partial view

Creates: /views/products/_sidebar.cfm

Custom template

Generated Code Examples

Default Template (index.cfm)

Form View (new.cfm)

Form Partial (_form.cfm)

Show View (show.cfm)

View Templates

Available Templates

Template Structure

Templates are located in:

Partial Views

Naming Convention

Partials start with underscore:

  • _form.cfm - Form partial

  • _item.cfm - List item partial

  • _sidebar.cfm - Sidebar partial

Generate Partials

Using Partials

Layout Integration

With Layout (default)

Without Layout

Custom Formats

HTML Format

Creates: /views/products/index.html

Custom Extensions

Creates: /views/emails/welcome.txt

Ajax Views

Generate AJAX View

AJAX Template Example

Form Helpers

Standard Form

File Upload Form

Responsive Design

Mobile-First Template

Localization

Generate Localized Views

Creates: /views/products/index_es.cfm

Localized Content

Testing Views

Generate View Tests

View Test Example

Performance Optimization

Caching Views

Lazy Loading

Best Practices

  1. Keep views simple and focused on presentation

  2. Use partials for reusable components

  3. Move complex logic to helpers or controllers

  4. Follow naming conventions consistently

  5. Use semantic HTML markup

  6. Include accessibility attributes

  7. Optimize for performance with caching

  8. Test views with various data states

Common Patterns

Empty State

Loading State

Error State

See Also

📚

- Generate applications, models, controllers, views

- Migrations and database management

- Run tests and generate coverage

- Manage application settings

🚀

- Understand the CLI's architecture

- Extend the CLI with your own commands

- Customize code generation templates

- Write and run tests effectively

- Database migration best practices

- Security scanning and hardening

- Optimization techniques

- All available configuration settings

- Variables available in templates

- Understanding command exit codes

- Environment configuration

Documentation:

GitHub:

Slack: - #wheels channel

Forums:

We welcome contributions! See our for details on:

- Complete command reference

- Get started in minutes

- Extend the CLI

- Technical deep dive

- Testing best practices

Wheels CLI is open source software licensed under the Apache License 2.0. See for details.

Ready to get started? Head to the or explore the .

- Initialize application configuration

- Auto-reload on file changes

- Configure reload settings

Argument
Description
Default
Option
Description

- Create a new Wheels application

- Reload the application

- Display version information

Option
Description

- Initialize a Wheels application

- Manage dependencies

Argument
Description
Required
Option
Description

- Generate controllers

- Generate models

- Generate scaffolding

Option
Description

- List installed plugins

- Initialize application

- Test after dependency updates

Argument
Description
Default
Option
Description
Default

- Manual application reload

- Run tests

- Configure watch settings

Option
Description
Default

- Non-interactive app generation

- Initialize existing directory

- Generate CRUD scaffolding

Argument
Description
Default
Option
Description
Default

- Initialize existing application

- Interactive app creation

- Generate CRUD scaffolding

Argument
Description
Default
Option
Description
Default

- Generate models

- Generate views

- Generate complete CRUD

- Generate controller tests

Argument
Description
Default
Option
Description
Default
Template
Description
Use Case

- Generate controllers

- Generate complete CRUD

- Generate view tests

Command Reference
Code Generation
Database Commands
Testing Commands
Configuration
And more...
Quick Start Guide
Service Architecture
Creating Custom Commands
Template System
Testing Guide
Migration Guide
Security Guide
Performance Guide
Configuration Options
Template Variables
Exit Codes
Environment Variables
https://docs.wheels.dev
https://github.com/wheels-dev/wheels
CFML Slack
https://groups.google.com/forum/#!forum/wheels
Contributing Guide
All Commands
Quick Start
Creating Commands
Service Architecture
Testing Guide
LICENSE
Quick Start Guide
Command Reference
Installation Guide
Quick Start Guide
Creating Custom Commands
CLI Architecture
wheels init
wheels watch
wheels config set
wheels init [name] [directory] [reload] [version] [createFolders]

name

Name of the application

Current directory name

directory

The directory to initialize

. (current directory)

reload

Reload password for the application

wheels

version

Wheels version to use

Latest stable

createFolders

Create Wheels directory structure

true

--help

Show help information

wheels init
wheels init myapp
wheels init myapp ./projects/myapp
wheels init myapp . secretPassword
wheels init myapp . wheels latest false
{
  "name": "myapp",
  "version": "2.5.0",
  "reload": "wheels",
  "framework": "wheels",
  "createFolders": true
}
wheels info

--help

Show help information

╔═══════════════════════════════════════════════╗
║           Wheels CLI Information              ║
╚═══════════════════════════════════════════════╝

CLI Version:      3.0.0
CommandBox:       5.9.0
Installation:     ~/.commandbox/cfml/modules/wheels-cli/

╔═══════════════════════════════════════════════╗
║         Application Information               ║
╚═══════════════════════════════════════════════╝

Application:      myapp
Wheels Version:   2.5.0
Reload Password:  [CONFIGURED]
Config File:      .wheels-cli.json

╔═══════════════════════════════════════════════╗
║         Environment Information               ║
╚═══════════════════════════════════════════════╝

CFML Engine:      Lucee 5.3.10.120
OS:              macOS 13.0
Java:            11.0.19
wheels destroy [type] [name] [options]

type

Type of resource to destroy

Yes

name

Name of the resource

Yes

--force

Skip confirmation prompts

--dry-run

Show what would be removed without doing it

--help

Show help information

wheels destroy controller users
wheels destroy model user
wheels destroy scaffold product
wheels destroy view users/edit
wheels destroy migration CreateUsersTable
wheels destroy scaffold order --dry-run
wheels destroy model tempdata --force
The following files will be removed:
- /models/User.cfc
- /tests/models/UserTest.cfc

Are you sure you want to proceed? (y/N):
# First, see what would be removed
wheels destroy scaffold product --dry-run

# If okay, proceed
wheels destroy scaffold product

# Remove the database table
wheels dbmigrate create remove_table products
wheels dbmigrate latest
# Accidentally created wrong controller
wheels generate controller userss  # Oops, typo!
wheels destroy controller userss   # Fix it
wheels generate controller users   # Create correct one
wheels deps

--help

Show help information

╔═══════════════════════════════════════════════╗
║          Dependency Analysis                  ║
╚═══════════════════════════════════════════════╝

CFML Dependencies (box.json):
✓ wheels          2.5.0    (up to date)
✓ cbvalidation    3.0.0    (up to date)
⚠ sqlbuilder      1.2.0    (1.3.0 available)
✗ testbox         Missing  (4.5.0 required)

Wheels Plugins:
✓ multimodule     1.0.0    (active)
✓ scaffold        2.1.0    (active)

Node Dependencies (package.json):
✓ webpack         5.88.0   (up to date)
✓ babel-core      7.22.0   (up to date)

Status: 1 missing, 1 outdated

Would you like to:
[1] Install missing dependencies
[2] Update outdated dependencies
[3] Update all dependencies
[4] Exit

Choice:
{
  "dependencies": {
    "wheels": "^2.5.0",
    "testbox": "^4.5.0"
  }
}
{
  "dependencies": {
    "webpack": "^5.88.0"
  }
}
# For CFML dependencies
box install

# For Node dependencies
npm install

# For Wheels plugins
wheels plugins list
wheels watch [path] [--extensions] [--exclude] [--reload-mode]

path

Directory to watch

. (current directory)

--extensions

File extensions to watch

.cfc,.cfm,.json

--exclude

Paths to exclude

temp/,logs/,.git/

--reload-mode

Reload mode when changes detected

development

--test

Run tests on change

false

--debounce

Milliseconds to wait before reacting

500

--help

Show help information

wheels watch
wheels watch ./src
wheels watch --extensions=".cfc,.cfm,.js,.css"
wheels watch --exclude="node_modules/,temp/,logs/"
wheels watch --test
wheels watch --debounce=1000
[Wheels Watch] Monitoring for changes...
[Wheels Watch] Watching: /Users/myapp
[Wheels Watch] Extensions: .cfc, .cfm, .json
[Wheels Watch] Excluded: temp/, logs/, .git/

[12:34:56] Change detected: models/User.cfc
[12:34:56] Reloading application...
[12:34:57] ✓ Application reloaded successfully

[12:35:23] Change detected: views/users/index.cfm
[12:35:23] Clearing view cache...
[12:35:23] ✓ View cache cleared

[12:36:45] Multiple changes detected:
  - controllers/Products.cfc
  - models/Product.cfc
[12:36:45] Reloading application...
[12:36:46] ✓ Application reloaded successfully
[12:36:46] Running tests...
[12:36:48] ✓ All tests passed (15 specs, 0 failures)
{
  "extensions": [".cfc", ".cfm", ".js", ".css"],
  "exclude": ["node_modules/", "temp/", ".git/", "logs/"],
  "reload": {
    "mode": "development",
    "debounce": 500
  },
  "test": {
    "enabled": true,
    "on": ["models/", "controllers/"],
    "command": "wheels test run"
  },
  "custom": [
    {
      "pattern": "assets/",
      "command": "npm run build"
    }
  ]
}
{
  "label": "Wheels Watch",
  "type": "shell",
  "command": "wheels watch",
  "problemMatcher": [],
  "isBackground": true
}
{
  "cmd": ["wheels", "watch"],
  "working_dir": "${project_path}"
}
# Terminal 1: Run server
box server start

# Terminal 2: Watch for changes
wheels watch --test
wheels watch --extensions=".cfc,.cfm,.js,.vue" \
  --custom='{"pattern":"src/","command":"npm run build"}'
wheels watch models/ controllers/ --test --reload-mode=testing
wheels generate app-wizard [options]
wheels g app-wizard [options]

--expert

Show advanced options

false

--skip-install

Skip dependency installation

false

--help

Show help information

? What is the name of your application? › myapp
? Which template would you like to use? › 
  ❯ Base - Minimal Wheels application
    Base@BE - Backend-only (no views)
    HelloWorld - Simple example application
    HelloDynamic - Database-driven example
    HelloPages - Static pages example
? Where should the application be created? › ./myapp
? Would you like to configure a database? (Y/n) › Y
? Database type? › 
  ❯ H2 (Embedded)
    MySQL
    PostgreSQL
    SQL Server
    Custom
? Select additional features: › 
  ◯ Bootstrap CSS framework
  ◯ jQuery library
  ◯ Sample authentication
  ◯ API documentation
  ◯ Docker configuration
? Which CFML engine will you use? › 
  ❯ Lucee 5
    Lucee 6
    Adobe ColdFusion 2018
    Adobe ColdFusion 2021
    Adobe ColdFusion 2023
? Set reload password (leave blank for 'wheels'): › ****
? Enable CSRF protection? (Y/n) › Y
? Enable secure cookies? (y/N) › N
Application Configuration:
─────────────────────────
Name:       myapp
Template:   Base
Directory:  ./myapp
Database:   H2 (Embedded)
Features:   Bootstrap, jQuery
Engine:     Lucee 5
Reload PWD: ****

? Create application with these settings? (Y/n) › Y
graph TD
    A[Start Wizard] --> B[Enter App Name]
    B --> C[Select Template]
    C --> D[Choose Directory]
    D --> E[Configure Database]
    E --> F[Select Features]
    F --> G[Choose CFML Engine]
    G --> H[Security Settings]
    H --> I[Review Configuration]
    I --> J{Confirm?}
    J -->|Yes| K[Create Application]
    J -->|No| B
    K --> L[Install Dependencies]
    L --> M[Show Next Steps]
wheels generate app-wizard --expert
? Save this configuration as a profile? (y/N) › Y
? Profile name: › enterprise-api
wheels generate app-wizard --profile=enterprise-api
wheels generate app-wizard --list-profiles
✓ Application created successfully!

Next steps:
1. cd myapp
2. box install (or run manually if skipped)
3. box server start
4. Visit http://localhost:3000

Additional commands:
- wheels test          Run tests
- wheels dbmigrate up  Run migrations
- wheels generate      Generate code
- wheels help          Show all commands
~/.wheels/templates/
├── my-template/
│   ├── template.json
│   ├── config/
│   ├── controllers/
│   └── views/
{
  "name": "My Custom Template",
  "description": "Custom template for specific use case",
  "author": "Your Name",
  "version": "1.0.0",
  "prompts": [
    {
      "name": "apiVersion",
      "message": "API version?",
      "default": "v1"
    }
  ]
}
wheels generate app-wizard --ci=github
wheels generate app-wizard --ide=vscode
wheels generate app [name] [template] [directory] [options]
wheels g app [name] [template] [directory] [options]
wheels new [name] [template] [directory] [options]

name

Application name

Required

template

Template to use

Base

directory

Target directory

./{name}

--reloadPassword

Set reload password

wheels

--datasourceName

Database datasource name

App name

--cfmlEngine

CFML engine (lucee/adobe)

lucee

--useBootstrap

Include Bootstrap CSS

false

--setupH2

Setup H2 embedded database

false

--init

Initialize as CLI-enabled app

true

--force

Overwrite existing directory

false

--help

Show help information

wheels generate app myapp
wheels generate app myapp Base@BE
wheels generate app myapp HelloWorld
wheels generate app myapp HelloDynamic
wheels generate app myapp HelloPages
wheels generate app blog
wheels generate app api Base@BE
wheels generate app myapp Base ./projects/
wheels generate app portfolio --useBootstrap
wheels generate app demo --setupH2
wheels generate app enterprise HelloDynamic ./apps/ \
  --reloadPassword=secret \
  --datasourceName=enterprise_db \
  --cfmlEngine=adobe \
  --useBootstrap \
  --setupH2
myapp/
├── .wheels-cli.json      # CLI configuration
├── box.json              # Dependencies
├── server.json           # Server configuration
├── Application.cfc       # Application settings
├── config/
│   ├── app.cfm          # App configuration
│   ├── routes.cfm       # URL routes
│   └── settings.cfm     # Framework settings
├── controllers/
│   └── Main.cfc         # Default controller
├── models/
├── views/
│   ├── layout.cfm       # Default layout
│   └── main/
│       └── index.cfm    # Home page
├── public/
│   ├── stylesheets/
│   ├── javascripts/
│   └── images/
├── tests/
└── wheels/              # Framework files
{
  "name": "myapp",
  "version": "1.0.0",
  "dependencies": {
    "wheels": "^2.5.0"
  }
}
{
  "web": {
    "http": {
      "port": 3000
    }
  },
  "app": {
    "cfengine": "lucee@5"
  }
}
{
  "name": "myapp",
  "version": "1.0.0",
  "framework": "wheels",
  "reload": "wheels"
}
wheels generate app myapp --setupH2
wheels generate app myapp --datasourceName=myapp_db
server set app.datasources.myapp_db={...}
cd myapp
box install
box server start
http://localhost:3000
mytemplate/
├── config/
├── controllers/
├── models/
├── views/
└── template.json
wheels generate controller [name] [actions] [options]
wheels g controller [name] [actions] [options]

name

Controller name (singular or plural)

Required

actions

Comma-separated list of actions

index

--rest

Generate RESTful actions

false

--api

Generate API controller (no views)

false

--force

Overwrite existing files

false

--help

Show help information

wheels generate controller products
wheels generate controller products index,show,new,create,edit,update,delete
wheels generate controller products --rest
wheels generate controller api/products --api
wheels generate controller reports dashboard,monthly,yearly,export
component extends="Controller" {

    function init() {
        // Constructor
    }

    function index() {
        products = model("Product").findAll();
    }

}
component extends="Controller" {

    function init() {
        // Constructor
    }

    function index() {
        products = model("Product").findAll();
    }

    function show() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            flashInsert(error="Product not found");
            redirectTo(action="index");
        }
    }

    function new() {
        product = model("Product").new();
    }

    function create() {
        product = model("Product").new(params.product);
        if (product.save()) {
            flashInsert(success="Product created successfully");
            redirectTo(action="index");
        } else {
            renderView(action="new");
        }
    }

    function edit() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            flashInsert(error="Product not found");
            redirectTo(action="index");
        }
    }

    function update() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.update(params.product)) {
            flashInsert(success="Product updated successfully");
            redirectTo(action="index");
        } else {
            renderView(action="edit");
        }
    }

    function delete() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.delete()) {
            flashInsert(success="Product deleted successfully");
        } else {
            flashInsert(error="Could not delete product");
        }
        redirectTo(action="index");
    }

}
component extends="Controller" {

    function init() {
        provides("json");
    }

    function index() {
        products = model("Product").findAll();
        renderWith(products);
    }

    function show() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product)) {
            renderWith(product);
        } else {
            renderWith({error: "Product not found"}, status=404);
        }
    }

    function create() {
        product = model("Product").new(params.product);
        if (product.save()) {
            renderWith(product, status=201);
        } else {
            renderWith({errors: product.allErrors()}, status=422);
        }
    }

    function update() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.update(params.product)) {
            renderWith(product);
        } else {
            renderWith({errors: product.allErrors()}, status=422);
        }
    }

    function delete() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.delete()) {
            renderWith({message: "Product deleted"});
        } else {
            renderWith({error: "Could not delete"}, status=400);
        }
    }

}
<h1>Products</h1>

<p>#linkTo(text="New Product", action="new")#</p>

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        <cfloop query="products">
            <tr>
                <td>#products.name#</td>
                <td>
                    #linkTo(text="Show", action="show", key=products.id)#
                    #linkTo(text="Edit", action="edit", key=products.id)#
                    #linkTo(text="Delete", action="delete", key=products.id, method="delete", confirm="Are you sure?")#
                </td>
            </tr>
        </cfloop>
    </tbody>
</table>
<cfset get(name="products", to="products##index")>
<cfset get(name="product", to="products##show")>
<cfset post(name="products", to="products##create")>
<cfset resources("products")>
<cfset namespace("api")>
    <cfset resources("products")>
</cfset>
wheels generate controller products --rest
wheels generate test controller products
function init() {
    filters(through="authenticate", except="index,show");
}

private function authenticate() {
    if (!session.isLoggedIn) {
        redirectTo(controller="sessions", action="new");
    }
}
function index() {
    products = model("Product").findAll(
        page=params.page ?: 1,
        perPage=25,
        order="createdAt DESC"
    );
}
function index() {
    if (StructKeyExists(params, "q")) {
        products = model("Product").findAll(
            where="name LIKE :search OR description LIKE :search",
            params={search: "%#params.q#%"}
        );
    } else {
        products = model("Product").findAll();
    }
}
wheels generate view [controller] [actions] [options]
wheels g view [controller] [actions] [options]

controller

Controller name (singular or plural)

Required

actions

Comma-separated list of actions

index

--template

View template to use

default

--layout

Include layout wrapper

true

--partial

Generate as partial (prefix with _)

false

--format

File format (cfm, htm, html)

cfm

--rest

Generate RESTful views

false

--force

Overwrite existing files

false

--help

Show help information

wheels generate view products index
wheels generate view products index,show,new,edit
wheels generate view products --rest
wheels generate view products sidebar --partial
wheels generate view reports dashboard --template=dashboard
<h1>Products</h1>

<p>
    #linkTo(text="New Product", action="new", class="btn btn-primary")#
</p>

<cfif products.recordCount>
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Created</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <cfoutput query="products">
                <tr>
                    <td>#products.id#</td>
                    <td>#products.name#</td>
                    <td>#dateFormat(products.createdAt, "mm/dd/yyyy")#</td>
                    <td>
                        #linkTo(text="View", action="show", key=products.id, class="btn btn-sm btn-info")#
                        #linkTo(text="Edit", action="edit", key=products.id, class="btn btn-sm btn-warning")#
                        #linkTo(text="Delete", action="delete", key=products.id, method="delete", confirm="Are you sure?", class="btn btn-sm btn-danger")#
                    </td>
                </tr>
            </cfoutput>
        </tbody>
    </table>
<cfelse>
    <p class="alert alert-info">No products found.</p>
</cfif>
<h1>New Product</h1>

#includePartial("/products/form")#
#startFormTag(action=formAction)#

    <cfif product.hasErrors()>
        <div class="alert alert-danger">
            <h4>Please correct the following errors:</h4>
            #errorMessagesFor("product")#
        </div>
    </cfif>

    <div class="form-group">
        #textFieldTag(name="product[name]", value=product.name, label="Name", class="form-control")#
    </div>

    <div class="form-group">
        #textAreaTag(name="product[description]", value=product.description, label="Description", class="form-control", rows=5)#
    </div>

    <div class="form-group">
        #numberFieldTag(name="product[price]", value=product.price, label="Price", class="form-control", step="0.01")#
    </div>

    <div class="form-group">
        #selectTag(name="product[categoryId]", options=categories, selected=product.categoryId, label="Category", class="form-control", includeBlank="-- Select Category --")#
    </div>

    <div class="form-group">
        #checkBoxTag(name="product[isActive]", checked=product.isActive, label="Active", value=1)#
    </div>

    <div class="form-actions">
        #submitTag(value=submitLabel, class="btn btn-primary")#
        #linkTo(text="Cancel", action="index", class="btn btn-secondary")#
    </div>

#endFormTag()#
<h1>Product Details</h1>

<div class="card">
    <div class="card-body">
        <h2 class="card-title">#product.name#</h2>
        
        <dl class="row">
            <dt class="col-sm-3">Description</dt>
            <dd class="col-sm-9">#product.description#</dd>
            
            <dt class="col-sm-3">Price</dt>
            <dd class="col-sm-9">#dollarFormat(product.price)#</dd>
            
            <dt class="col-sm-3">Category</dt>
            <dd class="col-sm-9">#product.category.name#</dd>
            
            <dt class="col-sm-3">Status</dt>
            <dd class="col-sm-9">
                <cfif product.isActive>
                    <span class="badge badge-success">Active</span>
                <cfelse>
                    <span class="badge badge-secondary">Inactive</span>
                </cfif>
            </dd>
            
            <dt class="col-sm-3">Created</dt>
            <dd class="col-sm-9">#dateTimeFormat(product.createdAt, "mmm dd, yyyy h:nn tt")#</dd>
            
            <dt class="col-sm-3">Updated</dt>
            <dd class="col-sm-9">#dateTimeFormat(product.updatedAt, "mmm dd, yyyy h:nn tt")#</dd>
        </dl>
    </div>
    <div class="card-footer">
        #linkTo(text="Edit", action="edit", key=product.id, class="btn btn-primary")#
        #linkTo(text="Delete", action="delete", key=product.id, method="delete", confirm="Are you sure?", class="btn btn-danger")#
        #linkTo(text="Back to List", action="index", class="btn btn-secondary")#
    </div>
</div>

default

Standard HTML structure

General purpose

bootstrap

Bootstrap 5 components

Modern web apps

tailwind

Tailwind CSS classes

Utility-first design

ajax

AJAX-enabled views

Dynamic updates

mobile

Mobile-optimized

Responsive design

print

Print-friendly layout

Reports

email

Email template

Notifications

~/.commandbox/cfml/modules/wheels-cli/templates/views/
├── default/
│   ├── index.cfm
│   ├── show.cfm
│   ├── new.cfm
│   ├── edit.cfm
│   └── _form.cfm
├── bootstrap/
└── custom/
wheels generate view shared header,footer,navigation --partial
<!--- In layout or view --->
#includePartial("/shared/header")#
#includePartial("/products/form", product=product)#
#includePartial(partial="item", query=products)#
<!--- Generated view assumes layout wrapper --->
<h1>Page Title</h1>
<p>Content here</p>
wheels generate view products standalone --layout=false
<!DOCTYPE html>
<html>
<head>
    <title>Standalone View</title>
</head>
<body>
    <h1>Products</h1>
    <!-- Complete HTML structure -->
</body>
</html>
wheels generate view products index --format=html
wheels generate view emails welcome --format=txt
wheels generate view products search --template=ajax
<cfif isAjax()>
    <!--- Return just the content --->
    <cfoutput query="products">
        <div class="search-result">
            <h3>#products.name#</h3>
            <p>#products.description#</p>
        </div>
    </cfoutput>
<cfelse>
    <!--- Include full page structure --->
    <div id="search-results">
        <cfinclude template="_results.cfm">
    </div>
</cfif>
#startFormTag(action="create", method="post", class="needs-validation")#
    #textField(objectName="product", property="name", label="Product Name", class="form-control", required=true)#
    #textArea(objectName="product", property="description", label="Description", class="form-control", rows=5)#
    #select(objectName="product", property="categoryId", options=categories, label="Category", class="form-control")#
    #submitTag(value="Save Product", class="btn btn-primary")#
#endFormTag()#
#startFormTag(action="upload", multipart=true)#
    #fileFieldTag(name="productImage", label="Product Image", accept="image/*", class="form-control")#
    #submitTag(value="Upload", class="btn btn-primary")#
#endFormTag()#
wheels generate view products index --template=mobile
<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <h1 class="h3">Products</h1>
        </div>
    </div>
    
    <div class="row">
        <cfoutput query="products">
            <div class="col-12 col-md-6 col-lg-4 mb-3">
                <div class="card h-100">
                    <div class="card-body">
                        <h5 class="card-title">#products.name#</h5>
                        <p class="card-text">#products.description#</p>
                        #linkTo(text="View", action="show", key=products.id, class="btn btn-primary btn-sm")#
                    </div>
                </div>
            </div>
        </cfoutput>
    </div>
</div>
wheels generate view products index --locale=es
<h1>#l("products.title")#</h1>
<p>#l("products.description")#</p>

#linkTo(text=l("buttons.new"), action="new", class="btn btn-primary")#
wheels generate view products index
wheels generate test view products/index
component extends="wheels.Test" {
    
    function test_index_displays_products() {
        products = model("Product").findAll(maxRows=5);
        result = renderView(view="/products/index", products=products, layout=false);
        
        assert(Find("<h1>Products</h1>", result));
        assert(Find("New Product", result));
        assertEquals(products.recordCount, ListLen(result, "<tr>") - 1);
    }
    
}
<cfcache action="cache" timespan="#CreateTimeSpan(0,1,0,0)#">
    <!--- Expensive view content --->
    #includePartial("products/list", products=products)#
</cfcache>
<div class="products-container" data-lazy-load="/products/more">
    <!--- Initial content --->
</div>

<script>
// Implement lazy loading
</script>
<cfif products.recordCount>
    <!--- Show products --->
<cfelse>
    <div class="empty-state">
        <h2>No products found</h2>
        <p>Get started by adding your first product.</p>
        #linkTo(text="Add Product", action="new", class="btn btn-primary")#
    </div>
</cfif>
<div class="loading-spinner" style="display: none;">
    <i class="fa fa-spinner fa-spin"></i> Loading...
</div>
<cfif structKeyExists(variables, "error")>
    <div class="alert alert-danger">
        <strong>Error:</strong> #error.message#
    </div>
</cfif>
wheels init
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
Details
wheels generate app
wheels reload
wheels info
wheels init
wheels deps
wheels generate controller
wheels generate model
wheels scaffold
wheels plugins list
wheels init
wheels test
wheels reload
wheels test run
wheels config set
wheels generate app
wheels init
wheels scaffold
wheels generate app-wizard
wheels scaffold
wheels generate model
wheels generate view
wheels scaffold
wheels generate test
wheels generate controller
wheels scaffold
wheels generate test

wheels generate model

Generate a model with properties, validations, and associations.

Synopsis

wheels generate model [name] [options]
wheels g model [name] [options]

Description

The wheels generate model command creates a new model CFC file with optional properties, associations, and database migrations. Models represent database tables and contain business logic, validations, and relationships.

Arguments

Argument
Description
Default

name

Model name (singular)

Required

Options

Option
Description
Default

--migration

Generate migration file

true

--properties

Properties list (name:type)

--belongs-to

Belongs to associations

--has-many

Has many associations

--has-one

Has one associations

--force

Overwrite existing files

false

--help

Show help information

Examples

Basic model

wheels generate model user

Creates:

  • /models/User.cfc

  • Migration file (if enabled)

Model with properties

wheels generate model user --properties="firstName:string,lastName:string,email:string,age:integer"

Model with associations

wheels generate model post --belongs-to="user" --has-many="comments"

Model without migration

wheels generate model setting --migration=false

Complex model

wheels generate model product \
  --properties="name:string,price:decimal,stock:integer,active:boolean" \
  --belongs-to="category,brand" \
  --has-many="reviews,orderItems"

Property Types

Type
Database Type
CFML Type

string

VARCHAR(255)

string

text

TEXT

string

integer

INTEGER

numeric

biginteger

BIGINT

numeric

float

FLOAT

numeric

decimal

DECIMAL(10,2)

numeric

boolean

BOOLEAN

boolean

date

DATE

date

datetime

DATETIME

date

timestamp

TIMESTAMP

date

binary

BLOB

binary

uuid

VARCHAR(35)

string

Generated Code

Basic Model

component extends="Model" {

    function init() {
        // Table name (optional if following conventions)
        table("users");
        
        // Validations
        validatesPresenceOf("email");
        validatesUniquenessOf("email");
        validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
        
        // Callbacks
        beforeCreate("setDefaultValues");
    }
    
    private function setDefaultValues() {
        if (!StructKeyExists(this, "createdAt")) {
            this.createdAt = Now();
        }
    }

}

Model with Properties

component extends="Model" {

    function init() {
        // Properties
        property(name="firstName", label="First Name");
        property(name="lastName", label="Last Name");
        property(name="email", label="Email Address");
        property(name="age", label="Age");
        
        // Validations
        validatesPresenceOf("firstName,lastName,email");
        validatesUniquenessOf("email");
        validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
        validatesNumericalityOf("age", onlyInteger=true, greaterThan=0, lessThan=150);
    }

}

Model with Associations

component extends="Model" {

    function init() {
        // Associations
        belongsTo("user");
        hasMany("comments", dependent="deleteAll");
        
        // Nested properties
        nestedProperties(associations="comments", allowDelete=true);
        
        // Validations
        validatesPresenceOf("title,content,userId");
        validatesLengthOf("title", maximum=255);
    }

}

Validations

Common validation methods:

// Presence
validatesPresenceOf("name,email");

// Uniqueness
validatesUniquenessOf("email,username");

// Format
validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
validatesFormatOf("phone", regex="^\d{3}-\d{3}-\d{4}$");

// Length
validatesLengthOf("username", minimum=3, maximum=20);
validatesLengthOf("bio", maximum=500);

// Numerical
validatesNumericalityOf("age", onlyInteger=true, greaterThan=0);
validatesNumericalityOf("price", greaterThan=0);

// Inclusion/Exclusion
validatesInclusionOf("status", list="active,inactive,pending");
validatesExclusionOf("username", list="admin,root,system");

// Confirmation
validatesConfirmationOf("password");

// Custom
validate("customValidation");

Associations

Belongs To

belongsTo("user");
belongsTo(name="author", modelName="user", foreignKey="authorId");

Has Many

hasMany("comments");
hasMany(name="posts", dependent="deleteAll", orderBy="createdAt DESC");

Has One

hasOne("profile");
hasOne(name="address", dependent="delete");

Many to Many

hasMany("categorizations");
hasMany(name="categories", through="categorizations");

Callbacks

Lifecycle callbacks:

// Before callbacks
beforeCreate("method1,method2");
beforeUpdate("method3");
beforeSave("method4");
beforeDelete("method5");
beforeValidation("method6");

// After callbacks
afterCreate("method7");
afterUpdate("method8");
afterSave("method9");
afterDelete("method10");
afterValidation("method11");
afterFind("method12");
afterInitialization("method13");

Generated Migration

When --migration is enabled:

component extends="wheels.migrator.Migration" {

    function up() {
        transaction {
            t = createTable("users");
            t.string("firstName");
            t.string("lastName");
            t.string("email");
            t.integer("age");
            t.timestamps();
            t.create();
            
            addIndex(table="users", columns="email", unique=true);
        }
    }

    function down() {
        transaction {
            dropTable("users");
        }
    }

}

Best Practices

  1. Naming: Use singular names (User, not Users)

  2. Properties: Define all database columns

  3. Validations: Add comprehensive validations

  4. Associations: Define all relationships

  5. Callbacks: Use for automatic behaviors

  6. Indexes: Add to migration for performance

Common Patterns

Soft Deletes

function init() {
    softDeletes();
}

Calculated Properties

function init() {
    property(name="fullName", sql="firstName + ' ' + lastName");
}

Scopes

function scopeActive() {
    return where("active = ?", [true]);
}

function scopeRecent(required numeric days=7) {
    return where("createdAt >= ?", [DateAdd("d", -arguments.days, Now())]);
}

Default Values

function init() {
    beforeCreate("setDefaults");
}

private function setDefaults() {
    if (!StructKeyExists(this, "status")) {
        this.status = "pending";
    }
    if (!StructKeyExists(this, "priority")) {
        this.priority = 5;
    }
}

Testing

Generate model tests:

wheels generate model user --properties="email:string,name:string"
wheels generate test model user

See Also

wheels generate route

Generate route definitions for your application.

Synopsis

wheels generate route [pattern] [options]
wheels g route [pattern] [options]

Description

The wheels generate route command helps you create route definitions in your Wheels application. It can generate individual routes, RESTful resources, nested routes, and complex routing patterns while maintaining proper syntax and organization in your routes file.

Arguments

Argument
Description
Default

pattern

Route pattern or resource name

Required

Options

Option
Description
Default

--to

Controller#action destination

Required for non-resource

--method

HTTP method (GET, POST, PUT, DELETE)

GET

--name

Route name for URL helpers

Auto-generated

--resource

Generate RESTful resource routes

false

--api

Generate API routes (no new/edit)

false

--nested

Parent resource for nesting

--only

Only include specific actions

All actions

--except

Exclude specific actions

--namespace

Wrap in namespace

--constraints

Add route constraints

--force

Add even if route exists

false

--help

Show help information

Examples

Basic Route

wheels generate route "/about" --to="pages#about" --name="about"

Generates in /config/routes.cfm:

<cfset get(name="about", pattern="/about", to="pages##about")>

POST Route

wheels generate route "/contact" --to="contact#send" --method="POST" --name="sendContact"

Generates:

<cfset post(name="sendContact", pattern="/contact", to="contact##send")>

RESTful Resource

wheels generate route products --resource

Generates:

<cfset resources("products")>

This creates all standard routes:

  • GET /products (index)

  • GET /products/new (new)

  • POST /products (create)

  • GET /products/:key (show)

  • GET /products/:key/edit (edit)

  • PUT/PATCH /products/:key (update)

  • DELETE /products/:key (delete)

API Resource

wheels generate route products --api

Generates:

<cfset resources(name="products", nested=false, except="new,edit")>

Nested Resources

wheels generate route comments --resource --nested="posts"

Generates:

<cfset resources("posts")>
    <cfset resources("comments")>
</cfset>

Creates routes like:

  • /posts/:postKey/comments

  • /posts/:postKey/comments/:key

Route Patterns

Dynamic Segments

wheels generate route "/users/[key]/profile" --to="users#profile" --name="userProfile"

Generates:

<cfset get(name="userProfile", pattern="/users/[key]/profile", to="users##profile")>

Optional Segments

wheels generate route "/blog/[year]/[month?]/[day?]" --to="blog#archive" --name="blogArchive"

Generates:

<cfset get(name="blogArchive", pattern="/blog/[year]/[month?]/[day?]", to="blog##archive")>

Wildcards

wheels generate route "/docs/*" --to="documentation#show" --name="docs"

Generates:

<cfset get(name="docs", pattern="/docs/*", to="documentation##show")>

Advanced Routing

With Constraints

wheels generate route "/users/[id]" --to="users#show" --constraints="id=[0-9]+"

Generates:

<cfset get(pattern="/users/[id]", to="users##show", constraints={id="[0-9]+"})>

Namespace Routes

wheels generate route users --resource --namespace="admin"

Generates:

<cfset namespace("admin")>
    <cfset resources("users")>
</cfset>

Module Routes

wheels generate route dashboard --resource --namespace="admin" --module="backend"

Generates:

<cfset module("backend")>
    <cfset namespace("admin")>
        <cfset resources("dashboard")>
    </cfset>
</cfset>

Shallow Nesting

wheels generate route comments --resource --nested="posts" --shallow

Generates:

<cfset resources("posts")>
    <cfset resources(name="comments", shallow=true)>
</cfset>

Custom Actions

Member Routes

wheels generate route "products/[key]/activate" --to="products#activate" --method="PUT" --member

Generates:

<cfset resources("products")>
    <cfset put(pattern="[key]/activate", to="products##activate", on="member")>
</cfset>

Collection Routes

wheels generate route "products/search" --to="products#search" --collection

Generates:

<cfset resources("products")>
    <cfset get(pattern="search", to="products##search", on="collection")>
</cfset>

Route Files Organization

Main Routes File

/config/routes.cfm:

<!--- 
    Routes Configuration
    Define your application routes below
--->

<!--- Public routes --->
<cfset get(name="home", pattern="/", to="main##index")>
<cfset get(name="about", pattern="/about", to="pages##about")>
<cfset get(name="contact", pattern="/contact", to="pages##contact")>
<cfset post(name="sendContact", pattern="/contact", to="pages##sendContact")>

<!--- Authentication --->
<cfset get(name="login", pattern="/login", to="sessions##new")>
<cfset post(name="createSession", pattern="/login", to="sessions##create")>
<cfset delete(name="logout", pattern="/logout", to="sessions##delete")>

<!--- Resources --->
<cfset resources("products")>
<cfset resources("categories")>

<!--- API routes --->
<cfset namespace("api")>
    <cfset namespace("v1")>
        <cfset resources(name="products", nested=false, except="new,edit")>
        <cfset resources(name="users", nested=false, except="new,edit")>
    </cfset>
</cfset>

<!--- Admin routes --->
<cfset namespace("admin")>
    <cfset get(name="adminDashboard", pattern="/", to="dashboard##index")>
    <cfset resources("users")>
    <cfset resources("products")>
    <cfset resources("orders")>
</cfset>

<!--- Catch-all route --->
<cfset get(pattern="*", to="errors##notFound")>

Route Helpers

Generated routes create URL helpers:

Basic Helpers

<!--- For route: get(name="about", pattern="/about", to="pages##about") --->
#linkTo(route="about", text="About Us")#
#urlFor(route="about")#
#redirectTo(route="about")#

Resource Helpers

<!--- For route: resources("products") --->
#linkTo(route="products", text="All Products")# <!--- /products --->
#linkTo(route="product", key=product.id, text="View")# <!--- /products/123 --->
#linkTo(route="newProduct", text="Add Product")# <!--- /products/new --->
#linkTo(route="editProduct", key=product.id, text="Edit")# <!--- /products/123/edit --->

#urlFor(route="products")# <!--- /products --->
#urlFor(route="product", key=123)# <!--- /products/123 --->

Nested Resource Helpers

<!--- For nested resources("posts") > resources("comments") --->
#linkTo(route="postComments", postKey=post.id, text="Comments")# <!--- /posts/1/comments --->
#linkTo(route="postComment", postKey=post.id, key=comment.id, text="View")# <!--- /posts/1/comments/5 --->

Route Constraints

Pattern Constraints

wheels generate route "/posts/[year]/[month]" --to="posts#archive" --constraints="year=[0-9]{4},month=[0-9]{2}"

Format Constraints

wheels generate route "/api/users" --to="api/users#index" --format="json"

Generates:

<cfset get(pattern="/api/users", to="api/users##index", format="json")>

Route Testing

Generate Route Tests

wheels generate route products --resource
wheels generate test routes products

Route Test Example

component extends="wheels.Test" {
    
    function test_products_routes() {
        // Test index route
        result = $resolve(path="/products", method="GET");
        assert(result.controller == "products");
        assert(result.action == "index");
        
        // Test show route
        result = $resolve(path="/products/123", method="GET");
        assert(result.controller == "products");
        assert(result.action == "show");
        assert(result.params.key == "123");
        
        // Test create route
        result = $resolve(path="/products", method="POST");
        assert(result.controller == "products");
        assert(result.action == "create");
    }
    
}

Route Debugging

List All Routes

wheels routes list

Test Specific Route

wheels routes test "/products/123" --method=GET

Output:

Route resolved:
  Controller: products
  Action: show
  Params: {key: "123"}
  Name: product

Best Practices

  1. Order matters: Place specific routes before generic ones

  2. Use RESTful routes: Prefer resources() over individual routes

  3. Name your routes: Always provide names for URL helpers

  4. Group related routes: Use namespaces and modules

  5. Add constraints: Validate dynamic segments

  6. Document complex routes: Add comments explaining purpose

  7. Test route resolution: Ensure routes work as expected

Common Patterns

Authentication Required

<!--- Public routes --->
<cfset get(name="home", pattern="/", to="main##index")>

<!--- Authenticated routes --->
<cfset namespace(name="authenticated", path="/app")>
    <!--- All routes here require authentication --->
    <cfset resources("projects")>
    <cfset resources("tasks")>
</cfset>

API Versioning

<cfset namespace("api")>
    <cfset namespace(name="v1", path="/v1")>
        <cfset resources(name="users", except="new,edit")>
    </cfset>
    <cfset namespace(name="v2", path="/v2")>
        <cfset resources(name="users", except="new,edit")>
    </cfset>
</cfset>

Subdomain Routing

<cfset subdomain("api")>
    <cfset resources("products")>
</cfset>

<cfset subdomain("admin")>
    <cfset resources("users")>
</cfset>

Redirect Routes

<cfset get(pattern="/old-about", redirect="/about")>
<cfset get(pattern="/products/category/[name]", redirect="/categories/[name]")>

Performance Considerations

  1. Route caching: Routes are cached in production

  2. Minimize regex: Complex patterns slow routing

  3. Avoid wildcards: Be specific when possible

  4. Order efficiently: Most-used routes first

Troubleshooting

Route Not Found

  • Check route order

  • Verify HTTP method

  • Test with wheels routes test

  • Check for typos in pattern

Naming Conflicts

  • Ensure unique route names

  • Check for duplicate patterns

  • Use namespaces to avoid conflicts

Parameter Issues

  • Verify parameter names match

  • Check constraint patterns

  • Test with various inputs

See Also

wheels generate property

Add properties to existing model files.

Synopsis

wheels generate property [model] [properties] [options]
wheels g property [model] [properties] [options]

Description

The wheels generate property command adds new properties to existing model files. It can add simple properties, associations, calculated properties, and validations while maintaining proper code formatting and structure.

Arguments

Argument
Description
Default

model

Model name to add properties to

Required

properties

Property definitions (name:type:options)

Required

Options

Option
Description
Default

--migrate

Generate migration for database changes

true

--validate

Add validation rules

true

--defaults

Include default values

false

--callbacks

Generate property callbacks

false

--force

Overwrite without confirmation

false

--help

Show help information

Property Syntax

Basic Format

propertyName:type:option1:option2

Supported Types

  • string - VARCHAR(255)

  • text - TEXT/CLOB

  • integer - INT

  • float - DECIMAL

  • boolean - BIT/BOOLEAN

  • date - DATE

  • datetime - DATETIME

  • timestamp - TIMESTAMP

  • binary - BLOB

Property Options

  • required - Not null

  • unique - Unique constraint

  • index - Create index

  • default=value - Default value

  • limit=n - Character limit

  • precision=n - Decimal precision

  • scale=n - Decimal scale

Examples

Add single property

wheels generate property user email:string:required:unique

Add multiple properties

wheels generate property product "sku:string:required:unique price:float:required stock:integer:default=0"

Add text property with validation

wheels generate property post content:text:required:limit=5000

Add association

wheels generate property order userId:integer:required:belongsTo=user

Add calculated property

wheels generate property user "fullName:calculated"

Generated Code Examples

Basic Property Addition

Before:

component extends="Model" {
    
    function init() {
        // Existing code
    }
    
}

After:

component extends="Model" {
    
    function init() {
        // Existing code
        
        // Properties
        property(name="email", sql="email");
        
        // Validations
        validatesPresenceOf(properties="email");
        validatesUniquenessOf(properties="email");
        validatesFormatOf(property="email", regEx="^[^@\s]+@[^@\s]+\.[^@\s]+$");
    }
    
}

Multiple Properties

Command:

wheels generate property product "name:string:required description:text price:float:required:default=0.00 inStock:boolean:default=true"

Generated:

component extends="Model" {
    
    function init() {
        // Properties
        property(name="name", sql="name");
        property(name="description", sql="description");
        property(name="price", sql="price", default=0.00);
        property(name="inStock", sql="in_stock", default=true);
        
        // Validations
        validatesPresenceOf(properties="name,price");
        validatesNumericalityOf(property="price", allowBlank=false, greaterThanOrEqualTo=0);
    }
    
}

Association Property

Command:

wheels generate property comment "userId:integer:required:belongsTo=user postId:integer:required:belongsTo=post"

Generated:

component extends="Model" {
    
    function init() {
        // Associations
        belongsTo(name="user", foreignKey="userId");
        belongsTo(name="post", foreignKey="postId");
        
        // Properties
        property(name="userId", sql="user_id");
        property(name="postId", sql="post_id");
        
        // Validations
        validatesPresenceOf(properties="userId,postId");
    }
    
}

Calculated Property

Command:

wheels generate property user fullName:calculated --callbacks

Generated:

component extends="Model" {
    
    function init() {
        // Properties
        property(name="fullName", sql="", calculated=true);
    }
    
    // Calculated property getter
    function getFullName() {
        return this.firstName & " " & this.lastName;
    }
    
}

Migration Generation

When --migrate=true (default), generates migration:

Migration File

db/migrate/[timestamp]_add_properties_to_[model].cfc:

component extends="wheels.migrator.Migration" hint="Add properties to product" {

    function up() {
        transaction {
            addColumn(table="products", columnName="sku", columnType="string", limit=50, null=false);
            addColumn(table="products", columnName="price", columnType="decimal", precision=10, scale=2, null=false, default=0.00);
            addColumn(table="products", columnName="stock", columnType="integer", null=true, default=0);
            
            addIndex(table="products", columnNames="sku", unique=true);
        }
    }
    
    function down() {
        transaction {
            removeIndex(table="products", columnNames="sku");
            removeColumn(table="products", columnName="stock");
            removeColumn(table="products", columnName="price");
            removeColumn(table="products", columnName="sku");
        }
    }

}

Validation Rules

Automatic Validations

Based on property type and options:

Type
Validations Applied

string:required

validatesPresenceOf, validatesLengthOf

string:unique

validatesUniquenessOf

email

validatesFormatOf with email regex

integer

validatesNumericalityOf(onlyInteger=true)

float

validatesNumericalityOf

boolean

validatesInclusionOf(list="true,false,0,1")

date

validatesFormatOf with date pattern

Custom Validations

Add custom validation rules:

wheels generate property user "age:integer:min=18:max=120"

Generated:

validatesNumericalityOf(property="age", greaterThanOrEqualTo=18, lessThanOrEqualTo=120);

Property Callbacks

Generate with callbacks:

wheels generate property user lastLoginAt:datetime --callbacks

Generated:

function init() {
    // Properties
    property(name="lastLoginAt", sql="last_login_at");
    
    // Callbacks
    beforeUpdate("updateLastLoginAt");
}

private function updateLastLoginAt() {
    if (hasChanged("lastLoginAt")) {
        // Custom logic here
    }
}

Complex Properties

Enum-like Property

wheels generate property order "status:string:default=pending:inclusion=pending,processing,shipped,delivered"

Generated:

property(name="status", sql="status", default="pending");
validatesInclusionOf(property="status", list="pending,processing,shipped,delivered");

File Upload Property

wheels generate property user "avatar:string:fileField"

Generated:

property(name="avatar", sql="avatar");

// In the init() method
afterSave("processAvatarUpload");
beforeDelete("deleteAvatarFile");

private function processAvatarUpload() {
    if (hasChanged("avatar") && isUploadedFile("avatar")) {
        // Handle file upload
    }
}

JSON Property

wheels generate property user "preferences:text:json"

Generated:

property(name="preferences", sql="preferences");

function getPreferences() {
    if (isJSON(this.preferences)) {
        return deserializeJSON(this.preferences);
    }
    return {};
}

function setPreferences(required struct value) {
    this.preferences = serializeJSON(arguments.value);
}

Property Modifiers

Encrypted Property

wheels generate property user "ssn:string:encrypted"

Generated:

property(name="ssn", sql="ssn");

beforeSave("encryptSSN");
afterFind("decryptSSN");

private function encryptSSN() {
    if (hasChanged("ssn") && Len(this.ssn)) {
        this.ssn = encrypt(this.ssn, application.encryptionKey);
    }
}

private function decryptSSN() {
    if (Len(this.ssn)) {
        this.ssn = decrypt(this.ssn, application.encryptionKey);
    }
}

Slugged Property

wheels generate property post "slug:string:unique:fromProperty=title"

Generated:

property(name="slug", sql="slug");
validatesUniquenessOf(property="slug");

beforeValidation("generateSlug");

private function generateSlug() {
    if (!Len(this.slug) && Len(this.title)) {
        this.slug = createSlug(this.title);
    }
}

private function createSlug(required string text) {
    return reReplace(
        lCase(trim(arguments.text)),
        "[^a-z0-9]+",
        "-",
        "all"
    );
}

Batch Operations

Add Multiple Related Properties

wheels generate property user "
    profile.bio:text
    profile.website:string
    profile.twitter:string
    profile.github:string
" --nested

Add Timestamped Properties

wheels generate property post "publishedAt:timestamp deletedAt:timestamp:nullable"

Integration with Existing Code

Preserve Existing Structure

The command intelligently adds properties without disrupting:

  • Existing properties

  • Current validations

  • Defined associations

  • Custom methods

  • Comments and formatting

Conflict Resolution

wheels generate property user email:string
> Property 'email' already exists. Options:
> 1. Skip this property
> 2. Update existing property
> 3. Add with different name
> Choice:

Best Practices

  1. Add properties incrementally

  2. Always generate migrations

  3. Include appropriate validations

  4. Use semantic property names

  5. Add indexes for query performance

  6. Consider default values carefully

  7. Document complex properties

Common Patterns

Soft Delete

wheels generate property model deletedAt:timestamp:nullable

Versioning

wheels generate property document "version:integer:default=1 versionedAt:timestamp"

Status Tracking

wheels generate property order "status:string:default=pending statusChangedAt:timestamp"

Audit Fields

wheels generate property model "createdBy:integer:belongsTo=user updatedBy:integer:belongsTo=user"

Testing

After adding properties:

# Run migration
wheels dbmigrate up

# Generate property tests
wheels generate test model user

# Run tests
wheels test

See Also

wheels generate api-resource

Generate a complete API resource with model, API controller, and routes.

⚠️ Note: This command is currently marked as broken/disabled in the codebase. The documentation below represents the intended functionality when the command is restored.

Synopsis

wheels generate api-resource [name] [properties] [options]
wheels g api-resource [name] [properties] [options]

Description

The wheels generate api-resource command creates a complete RESTful API resource including model, API-specific controller (no views), routes, and optionally database migrations and tests. It's optimized for building JSON APIs following REST conventions.

Current Status

This command is temporarily disabled. Use alternative approaches:

# Option 1: Use regular resource with --api flag
wheels generate resource product name:string price:float --api

# Option 2: Generate components separately
wheels generate model product name:string price:float
wheels generate controller api/products --api
wheels generate route products --api --namespace=api

Arguments (When Enabled)

Argument
Description
Default

name

Resource name (typically singular)

Required

properties

Property definitions (name:type)

Options (When Enabled)

Option
Description
Default

--version

API version (v1, v2, etc.)

v1

--format

Response format (json, xml)

json

--auth

Include authentication

false

--pagination

Include pagination

true

--filtering

Include filtering

true

--sorting

Include sorting

true

--skip-model

Skip model generation

false

--skip-migration

Skip migration generation

false

--skip-tests

Skip test generation

false

--namespace

API namespace

api

--force

Overwrite existing files

false

--help

Show help information

Intended Functionality

Basic API Resource

wheels generate api-resource product name:string price:float description:text

Would generate:

  • Model: /models/Product.cfc

  • Controller: /controllers/api/v1/Products.cfc

  • Route: API namespace with versioning

  • Migration: Database migration file

  • Tests: API integration tests

Generated API Controller

/controllers/api/v1/Products.cfc:

component extends="Controller" {
    
    function init() {
        provides("json");
        
        // Filters
        filters(through="authenticate", except="index,show");
        filters(through="findProduct", only="show,update,delete");
        filters(through="parseApiParams", only="index");
    }
    
    function index() {
        // Pagination
        page = params.page ?: 1;
        perPage = Min(params.perPage ?: 25, 100);
        
        // Filtering
        where = [];
        if (StructKeyExists(params, "filter")) {
            if (StructKeyExists(params.filter, "name")) {
                ArrayAppend(where, "name LIKE :name");
                params.name = "%#params.filter.name#%";
            }
            if (StructKeyExists(params.filter, "minPrice")) {
                ArrayAppend(where, "price >= :minPrice");
                params.minPrice = params.filter.minPrice;
            }
        }
        
        // Sorting
        order = "createdAt DESC";
        if (StructKeyExists(params, "sort")) {
            order = parseSort(params.sort);
        }
        
        // Query
        products = model("Product").findAll(
            where=ArrayToList(where, " AND "),
            order=order,
            page=page,
            perPage=perPage,
            returnAs="objects"
        );
        
        // Response
        renderWith({
            data: serializeProducts(products),
            meta: {
                pagination: {
                    page: products.currentPage,
                    perPage: products.perPage,
                    total: products.totalRecords,
                    pages: products.totalPages
                }
            },
            links: {
                self: urlFor(route="apiV1Products", params=params),
                first: urlFor(route="apiV1Products", params=params, page=1),
                last: urlFor(route="apiV1Products", params=params, page=products.totalPages),
                prev: products.currentPage > 1 ? urlFor(route="apiV1Products", params=params, page=products.currentPage-1) : "",
                next: products.currentPage < products.totalPages ? urlFor(route="apiV1Products", params=params, page=products.currentPage+1) : ""
            }
        });
    }
    
    function show() {
        renderWith({
            data: serializeProduct(product),
            links: {
                self: urlFor(route="apiV1Product", key=product.id)
            }
        });
    }
    
    function create() {
        product = model("Product").new(deserializeProduct(params));
        
        if (product.save()) {
            renderWith(
                data={
                    data: serializeProduct(product),
                    links: {
                        self: urlFor(route="apiV1Product", key=product.id)
                    }
                },
                status=201,
                headers={"Location": urlFor(route="apiV1Product", key=product.id)}
            );
        } else {
            renderWith(
                data={
                    errors: formatErrors(product.allErrors())
                },
                status=422
            );
        }
    }
    
    function update() {
        if (product.update(deserializeProduct(params))) {
            renderWith({
                data: serializeProduct(product),
                links: {
                    self: urlFor(route="apiV1Product", key=product.id)
                }
            });
        } else {
            renderWith(
                data={
                    errors: formatErrors(product.allErrors())
                },
                status=422
            );
        }
    }
    
    function delete() {
        if (product.delete()) {
            renderWith(data={}, status=204);
        } else {
            renderWith(
                data={
                    errors: [{
                        status: "400",
                        title: "Bad Request",
                        detail: "Could not delete product"
                    }]
                },
                status=400
            );
        }
    }
    
    // Private methods
    
    private function findProduct() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            renderWith(
                data={
                    errors: [{
                        status: "404",
                        title: "Not Found",
                        detail: "Product not found"
                    }]
                },
                status=404
            );
        }
    }
    
    private function authenticate() {
        if (!StructKeyExists(headers, "Authorization")) {
            renderWith(
                data={
                    errors: [{
                        status: "401",
                        title: "Unauthorized",
                        detail: "Missing authentication"
                    }]
                },
                status=401
            );
        }
        // Implement authentication logic
    }
    
    private function parseApiParams() {
        // Parse JSON API params
        if (StructKeyExists(params, "_json")) {
            StructAppend(params, params._json, true);
        }
    }
    
    private function parseSort(required string sort) {
        local.allowedFields = ["name", "price", "createdAt"];
        local.parts = ListToArray(arguments.sort);
        local.order = [];
        
        for (local.part in local.parts) {
            local.desc = Left(local.part, 1) == "-";
            local.field = local.desc ? Right(local.part, Len(local.part)-1) : local.part;
            
            if (ArrayFindNoCase(local.allowedFields, local.field)) {
                ArrayAppend(local.order, local.field & (local.desc ? " DESC" : " ASC"));
            }
        }
        
        return ArrayToList(local.order);
    }
    
    private function serializeProducts(required array products) {
        local.result = [];
        for (local.product in arguments.products) {
            ArrayAppend(local.result, serializeProduct(local.product));
        }
        return local.result;
    }
    
    private function serializeProduct(required any product) {
        return {
            type: "products",
            id: arguments.product.id,
            attributes: {
                name: arguments.product.name,
                price: arguments.product.price,
                description: arguments.product.description,
                createdAt: DateTimeFormat(arguments.product.createdAt, "iso"),
                updatedAt: DateTimeFormat(arguments.product.updatedAt, "iso")
            },
            links: {
                self: urlFor(route="apiV1Product", key=arguments.product.id)
            }
        };
    }
    
    private function deserializeProduct(required struct data) {
        if (StructKeyExists(arguments.data, "data")) {
            return arguments.data.data.attributes;
        }
        return arguments.data;
    }
    
    private function formatErrors(required array errors) {
        local.result = [];
        for (local.error in arguments.errors) {
            ArrayAppend(local.result, {
                status: "422",
                source: {pointer: "/data/attributes/#local.error.property#"},
                title: "Validation Error",
                detail: local.error.message
            });
        }
        return local.result;
    }
    
}

API Routes

Generated in /config/routes.cfm:

<cfset namespace("api")>
    <cfset namespace("v1")>
        <cfset resources(name="products", except="new,edit")>
        
        <!--- Additional API routes --->
        <cfset post(pattern="products/[key]/activate", to="products##activate", on="member")>
        <cfset get(pattern="products/search", to="products##search", on="collection")>
    </cfset>
</cfset>

API Documentation

Would generate OpenAPI/Swagger documentation:

openapi: 3.0.0
info:
  title: Products API
  version: 1.0.0
  
paths:
  /api/v1/products:
    get:
      summary: List products
      parameters:
        - name: page
          in: query
          schema:
            type: integer
        - name: perPage
          in: query
          schema:
            type: integer
        - name: filter[name]
          in: query
          schema:
            type: string
        - name: sort
          in: query
          schema:
            type: string
      responses:
        200:
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductList'
    
    post:
      summary: Create product
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductInput'
      responses:
        201:
          description: Created
        422:
          description: Validation error

Workaround Implementation

Until the command is fixed, implement API resources manually:

1. Generate Model

wheels generate model product name:string price:float description:text

2. Create API Controller

Create /controllers/api/v1/Products.cfc manually with the code above.

3. Add Routes

<!--- In /config/routes.cfm --->
<cfset namespace("api")>
    <cfset namespace("v1")>
        <cfset resources(name="products", except="new,edit")>
    </cfset>
</cfset>

4. Create Tests

wheels generate test controller api/v1/products

API Features

Authentication

// Bearer token authentication
private function authenticate() {
    local.token = GetHttpRequestData().headers["Authorization"] ?: "";
    local.token = ReReplace(local.token, "^Bearer\s+", "");
    
    if (!Len(local.token) || !isValidToken(local.token)) {
        renderWith(
            data={error: "Unauthorized"},
            status=401
        );
    }
}

Rate Limiting

// In controller init()
filters(through="rateLimit");

private function rateLimit() {
    local.key = "api_rate_limit_" & request.remoteAddress;
    local.limit = 100; // requests per hour
    
    if (!StructKeyExists(application, local.key)) {
        application[local.key] = {
            count: 0,
            reset: DateAdd("h", 1, Now())
        };
    }
    
    if (application[local.key].reset < Now()) {
        application[local.key] = {
            count: 0,
            reset: DateAdd("h", 1, Now())
        };
    }
    
    application[local.key].count++;
    
    if (application[local.key].count > local.limit) {
        renderWith(
            data={error: "Rate limit exceeded"},
            status=429,
            headers={
                "X-RateLimit-Limit": local.limit,
                "X-RateLimit-Remaining": 0,
                "X-RateLimit-Reset": DateTimeFormat(application[local.key].reset, "iso")
            }
        );
    }
}

CORS Headers

// In controller init()
filters(through="setCorsHeaders");

private function setCorsHeaders() {
    header name="Access-Control-Allow-Origin" value="*";
    header name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS";
    header name="Access-Control-Allow-Headers" value="Content-Type, Authorization";
    
    if (request.method == "OPTIONS") {
        renderWith(data={}, status=200);
    }
}

Testing API Resources

Integration Tests

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        model("Product").deleteAll();
    }
    
    function test_get_products_returns_json() {
        products = createProducts(3);
        
        result = $request(
            route="apiV1Products",
            method="GET",
            headers={"Accept": "application/json"}
        );
        
        assert(result.status == 200);
        data = DeserializeJSON(result.body);
        assert(ArrayLen(data.data) == 3);
        assert(data.meta.pagination.total == 3);
    }
    
    function test_create_product_with_valid_data() {
        params = {
            data: {
                type: "products",
                attributes: {
                    name: "Test Product",
                    price: 29.99,
                    description: "Test description"
                }
            }
        };
        
        result = $request(
            route="apiV1Products",
            method="POST",
            params=params,
            headers={
                "Content-Type": "application/json",
                "Accept": "application/json"
            }
        );
        
        assert(result.status == 201);
        assert(StructKeyExists(result.headers, "Location"));
        data = DeserializeJSON(result.body);
        assert(data.data.attributes.name == "Test Product");
    }
    
    function test_authentication_required() {
        result = $request(
            route="apiV1Products",
            method="POST",
            params={},
            headers={"Accept": "application/json"}
        );
        
        assert(result.status == 401);
    }
    
}

Best Practices

  1. Version your API: Use URL versioning (/api/v1/)

  2. Use consistent formats: JSON API or custom format

  3. Include pagination: Limit response sizes

  4. Add filtering: Allow query parameters

  5. Implement sorting: Support field sorting

  6. Handle errors consistently: Standard error format

  7. Document thoroughly: OpenAPI/Swagger specs

  8. Add authentication: Secure endpoints

  9. Rate limit: Prevent abuse

  10. Test extensively: Integration tests

See Also

wheels generate frontend

Generate frontend code including JavaScript, CSS, and interactive components.

⚠️ Note: This command is currently marked as disabled in the codebase. The documentation below represents the intended functionality when the command is restored.

Synopsis

wheels generate frontend [type] [name] [options]
wheels g frontend [type] [name] [options]

Description

The wheels generate frontend command creates frontend assets including JavaScript modules, CSS stylesheets, and interactive components. It supports various frontend frameworks and patterns while integrating seamlessly with Wheels views.

Current Status

This command is temporarily disabled. Use manual approaches:

# Create frontend files manually in:
# /public/javascripts/
# /public/stylesheets/
# /views/components/

Arguments (When Enabled)

Argument
Description
Default

type

Type of frontend asset (component, module, style)

Required

name

Name of the asset

Required

Options (When Enabled)

Option
Description
Default

--framework

Frontend framework (vanilla, alpine, vue, htmx)

vanilla

--style

CSS framework (none, bootstrap, tailwind)

none

--bundler

Use bundler (webpack, vite, none)

none

--typescript

Generate TypeScript files

false

--test

Generate test files

true

--force

Overwrite existing files

false

--help

Show help information

Intended Functionality

Generate Component

wheels generate frontend component productCard --framework=alpine

Would generate:

/public/javascripts/components/productCard.js:

// Product Card Component
document.addEventListener('alpine:init', () => {
    Alpine.data('productCard', (initialData = {}) => ({
        // State
        product: initialData.product || {},
        isLoading: false,
        isFavorite: false,
        
        // Computed
        get formattedPrice() {
            return new Intl.NumberFormat('en-US', {
                style: 'currency',
                currency: 'USD'
            }).format(this.product.price || 0);
        },
        
        // Methods
        async toggleFavorite() {
            this.isLoading = true;
            try {
                const response = await fetch(`/api/products/${this.product.id}/favorite`, {
                    method: this.isFavorite ? 'DELETE' : 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-Token': this.getCsrfToken()
                    }
                });
                
                if (response.ok) {
                    this.isFavorite = !this.isFavorite;
                    this.$dispatch('favorite-changed', {
                        productId: this.product.id,
                        isFavorite: this.isFavorite
                    });
                }
            } catch (error) {
                console.error('Failed to toggle favorite:', error);
                this.$dispatch('notification', {
                    type: 'error',
                    message: 'Failed to update favorite status'
                });
            } finally {
                this.isLoading = false;
            }
        },
        
        async addToCart() {
            this.isLoading = true;
            try {
                const response = await fetch('/api/cart/items', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-Token': this.getCsrfToken()
                    },
                    body: JSON.stringify({
                        productId: this.product.id,
                        quantity: 1
                    })
                });
                
                if (response.ok) {
                    this.$dispatch('cart-updated');
                    this.$dispatch('notification', {
                        type: 'success',
                        message: 'Added to cart'
                    });
                }
            } catch (error) {
                console.error('Failed to add to cart:', error);
            } finally {
                this.isLoading = false;
            }
        },
        
        getCsrfToken() {
            return document.querySelector('meta[name="csrf-token"]')?.content || '';
        }
    }));
});

/public/stylesheets/components/productCard.css:

/* Product Card Styles */
.product-card {
    @apply bg-white rounded-lg shadow-md overflow-hidden transition-transform hover:scale-105;
}

.product-card__image {
    @apply w-full h-48 object-cover;
}

.product-card__content {
    @apply p-4;
}

.product-card__title {
    @apply text-lg font-semibold text-gray-800 mb-2;
}

.product-card__price {
    @apply text-xl font-bold text-blue-600 mb-3;
}

.product-card__actions {
    @apply flex justify-between items-center;
}

.product-card__button {
    @apply px-4 py-2 rounded font-medium transition-colors;
}

.product-card__button--primary {
    @apply bg-blue-500 text-white hover:bg-blue-600;
}

.product-card__button--secondary {
    @apply bg-gray-200 text-gray-700 hover:bg-gray-300;
}

.product-card__button:disabled {
    @apply opacity-50 cursor-not-allowed;
}

/views/components/_productCard.cfm:

<div class="product-card" 
     x-data="productCard({
         product: #SerializeJSON({
             id: arguments.product.id,
             name: arguments.product.name,
             price: arguments.product.price,
             image: arguments.product.imageUrl
         })#
     })">
    
    <img :src="product.image" 
         :alt="product.name" 
         class="product-card__image">
    
    <div class="product-card__content">
        <h3 class="product-card__title" x-text="product.name"></h3>
        <p class="product-card__price" x-text="formattedPrice"></p>
        
        <div class="product-card__actions">
            <button @click="addToCart" 
                    :disabled="isLoading"
                    class="product-card__button product-card__button--primary">
                <span x-show="!isLoading">Add to Cart</span>
                <span x-show="isLoading">Adding...</span>
            </button>
            
            <button @click="toggleFavorite" 
                    :disabled="isLoading"
                    class="product-card__button product-card__button--secondary">
                <span x-show="!isFavorite">♡</span>
                <span x-show="isFavorite">♥</span>
            </button>
        </div>
    </div>
</div>

Generate JavaScript Module

wheels generate frontend module api --typescript

Would generate /public/javascripts/modules/api.ts:

// API Module
interface RequestOptions extends RequestInit {
    params?: Record<string, any>;
}

interface ApiResponse<T = any> {
    data: T;
    meta?: Record<string, any>;
    errors?: Array<{
        field: string;
        message: string;
    }>;
}

class ApiClient {
    private baseUrl: string;
    private defaultHeaders: Record<string, string>;
    
    constructor(baseUrl: string = '/api') {
        this.baseUrl = baseUrl;
        this.defaultHeaders = {
            'Content-Type': 'application/json',
            'X-Requested-With': 'XMLHttpRequest'
        };
        
        // Add CSRF token if available
        const csrfToken = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
        if (csrfToken) {
            this.defaultHeaders['X-CSRF-Token'] = csrfToken;
        }
    }
    
    async request<T = any>(
        endpoint: string,
        options: RequestOptions = {}
    ): Promise<ApiResponse<T>> {
        const { params, ...fetchOptions } = options;
        
        // Build URL with params
        const url = new URL(this.baseUrl + endpoint, window.location.origin);
        if (params) {
            Object.entries(params).forEach(([key, value]) => {
                if (value !== undefined && value !== null) {
                    url.searchParams.append(key, String(value));
                }
            });
        }
        
        // Merge headers
        const headers = {
            ...this.defaultHeaders,
            ...fetchOptions.headers
        };
        
        try {
            const response = await fetch(url.toString(), {
                ...fetchOptions,
                headers
            });
            
            if (!response.ok) {
                throw new ApiError(response.status, await response.text());
            }
            
            const data = await response.json();
            return data;
            
        } catch (error) {
            if (error instanceof ApiError) {
                throw error;
            }
            throw new ApiError(0, 'Network error');
        }
    }
    
    get<T = any>(endpoint: string, params?: Record<string, any>): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, { method: 'GET', params });
    }
    
    post<T = any>(endpoint: string, data?: any): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
    
    put<T = any>(endpoint: string, data?: any): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }
    
    delete<T = any>(endpoint: string): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, { method: 'DELETE' });
    }
}

class ApiError extends Error {
    constructor(public status: number, message: string) {
        super(message);
        this.name = 'ApiError';
    }
}

// Export singleton instance
export const api = new ApiClient();

// Export types
export type { ApiResponse, RequestOptions };
export { ApiClient, ApiError };

Generate HTMX Component

wheels generate frontend component searchForm --framework=htmx

Would generate:

/views/components/_searchForm.cfm:

<form hx-get="/products/search" 
      hx-trigger="submit, keyup changed delay:500ms from:input[name='q']"
      hx-target="##search-results"
      hx-indicator="##search-spinner"
      class="search-form">
    
    <div class="search-form__input-group">
        <input type="search" 
               name="q" 
               value="#params.q ?: ''#"
               placeholder="Search products..."
               class="search-form__input">
        
        <button type="submit" class="search-form__button">
            Search
        </button>
    </div>
    
    <div class="search-form__filters">
        <select name="category" 
                hx-get="/products/search"
                hx-trigger="change"
                hx-target="##search-results"
                hx-include="[name='q']"
                class="search-form__select">
            <option value="">All Categories</option>
            <cfloop query="categories">
                <option value="#categories.id#" 
                        <cfif params.category == categories.id>selected</cfif>>
                    #categories.name#
                </option>
            </cfloop>
        </select>
        
        <select name="sort" 
                hx-get="/products/search"
                hx-trigger="change"
                hx-target="##search-results"
                hx-include="[name='q'],[name='category']"
                class="search-form__select">
            <option value="relevance">Relevance</option>
            <option value="price-asc">Price: Low to High</option>
            <option value="price-desc">Price: High to Low</option>
            <option value="name">Name</option>
        </select>
    </div>
</form>

<div id="search-spinner" class="htmx-indicator">
    <div class="spinner"></div>
</div>

<div id="search-results">
    <!--- Results will be loaded here --->
</div>

Generate Vue Component

wheels generate frontend component todoList --framework=vue

Would generate /public/javascripts/components/TodoList.vue:

<template>
  <div class="todo-list">
    <h2>{{ title }}</h2>
    
    <form @submit.prevent="addTodo" class="todo-form">
      <input
        v-model="newTodo"
        type="text"
        placeholder="Add a new todo..."
        class="todo-form__input"
      >
      <button type="submit" class="todo-form__button">
        Add
      </button>
    </form>
    
    <ul class="todo-items">
      <li
        v-for="todo in filteredTodos"
        :key="todo.id"
        class="todo-item"
        :class="{ 'todo-item--completed': todo.completed }"
      >
        <input
          type="checkbox"
          v-model="todo.completed"
          @change="updateTodo(todo)"
        >
        <span class="todo-item__text">{{ todo.text }}</span>
        <button
          @click="deleteTodo(todo.id)"
          class="todo-item__delete"
        >
          ×
        </button>
      </li>
    </ul>
    
    <div class="todo-filters">
      <button
        v-for="filter in filters"
        :key="filter"
        @click="currentFilter = filter"
        :class="{ active: currentFilter === filter }"
        class="todo-filter"
      >
        {{ filter }}
      </button>
    </div>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue';
import { api } from '../modules/api';

export default {
  name: 'TodoList',
  props: {
    title: {
      type: String,
      default: 'Todo List'
    }
  },
  setup() {
    const todos = ref([]);
    const newTodo = ref('');
    const currentFilter = ref('all');
    const filters = ['all', 'active', 'completed'];
    
    const filteredTodos = computed(() => {
      switch (currentFilter.value) {
        case 'active':
          return todos.value.filter(todo => !todo.completed);
        case 'completed':
          return todos.value.filter(todo => todo.completed);
        default:
          return todos.value;
      }
    });
    
    const loadTodos = async () => {
      try {
        const response = await api.get('/todos');
        todos.value = response.data;
      } catch (error) {
        console.error('Failed to load todos:', error);
      }
    };
    
    const addTodo = async () => {
      if (!newTodo.value.trim()) return;
      
      try {
        const response = await api.post('/todos', {
          text: newTodo.value,
          completed: false
        });
        todos.value.push(response.data);
        newTodo.value = '';
      } catch (error) {
        console.error('Failed to add todo:', error);
      }
    };
    
    const updateTodo = async (todo) => {
      try {
        await api.put(`/todos/${todo.id}`, {
          completed: todo.completed
        });
      } catch (error) {
        console.error('Failed to update todo:', error);
        todo.completed = !todo.completed;
      }
    };
    
    const deleteTodo = async (id) => {
      try {
        await api.delete(`/todos/${id}`);
        todos.value = todos.value.filter(todo => todo.id !== id);
      } catch (error) {
        console.error('Failed to delete todo:', error);
      }
    };
    
    onMounted(loadTodos);
    
    return {
      todos,
      newTodo,
      currentFilter,
      filters,
      filteredTodos,
      addTodo,
      updateTodo,
      deleteTodo
    };
  }
};
</script>

<style scoped>
.todo-list {
  max-width: 500px;
  margin: 0 auto;
}

.todo-form {
  display: flex;
  margin-bottom: 1rem;
}

.todo-form__input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
}

.todo-form__button {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}

.todo-items {
  list-style: none;
  padding: 0;
}

.todo-item {
  display: flex;
  align-items: center;
  padding: 0.5rem;
  border-bottom: 1px solid #eee;
}

.todo-item--completed .todo-item__text {
  text-decoration: line-through;
  opacity: 0.6;
}

.todo-item__delete {
  margin-left: auto;
  background: none;
  border: none;
  color: #dc3545;
  font-size: 1.5rem;
  cursor: pointer;
}

.todo-filters {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.todo-filter {
  padding: 0.25rem 0.75rem;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
}

.todo-filter.active {
  background: #007bff;
  color: white;
  border-color: #007bff;
}
</style>

Workaround Implementation

Until the command is fixed, create frontend assets manually:

1. Directory Structure

/public/
├── javascripts/
│   ├── app.js
│   ├── components/
│   ├── modules/
│   └── vendor/
├── stylesheets/
│   ├── app.css
│   ├── components/
│   └── vendor/
└── images/

2. Basic App Structure

/public/javascripts/app.js:

// Main application JavaScript
(function() {
    'use strict';
    
    // Initialize on DOM ready
    document.addEventListener('DOMContentLoaded', function() {
        initializeComponents();
        setupEventListeners();
        loadDynamicContent();
    });
    
    function initializeComponents() {
        // Initialize all components
        document.querySelectorAll('[data-component]').forEach(element => {
            const componentName = element.dataset.component;
            if (window.components && window.components[componentName]) {
                new window.components[componentName](element);
            }
        });
    }
    
    function setupEventListeners() {
        // Global event delegation
        document.addEventListener('click', handleClick);
        document.addEventListener('submit', handleSubmit);
    }
    
    function handleClick(event) {
        // Handle data-action clicks
        const action = event.target.closest('[data-action]');
        if (action) {
            event.preventDefault();
            const actionName = action.dataset.action;
            // Handle action
        }
    }
    
    function handleSubmit(event) {
        // Handle AJAX forms
        const form = event.target;
        if (form.dataset.remote === 'true') {
            event.preventDefault();
            submitFormAjax(form);
        }
    }
    
    function submitFormAjax(form) {
        const formData = new FormData(form);
        
        fetch(form.action, {
            method: form.method,
            body: formData,
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        })
        .then(response => response.json())
        .then(data => {
            // Handle response
        })
        .catch(error => {
            console.error('Form submission error:', error);
        });
    }
    
    function loadDynamicContent() {
        // Load content marked for lazy loading
        const lazyElements = document.querySelectorAll('[data-lazy-load]');
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    loadContent(entry.target);
                    observer.unobserve(entry.target);
                }
            });
        });
        
        lazyElements.forEach(el => observer.observe(el));
    }
    
    function loadContent(element) {
        const url = element.dataset.lazyLoad;
        fetch(url)
            .then(response => response.text())
            .then(html => {
                element.innerHTML = html;
                initializeComponents();
            });
    }
    
})();

3. CSS Structure

/public/stylesheets/app.css:

/* Base styles */
:root {
    --primary-color: #007bff;
    --secondary-color: #6c757d;
    --success-color: #28a745;
    --danger-color: #dc3545;
    --warning-color: #ffc107;
    --info-color: #17a2b8;
    --light-color: #f8f9fa;
    --dark-color: #343a40;
}

/* Layout */
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1rem;
}

/* Components */
@import 'components/buttons.css';
@import 'components/forms.css';
@import 'components/cards.css';
@import 'components/modals.css';

/* Utilities */
.hidden {
    display: none !important;
}

.loading {
    opacity: 0.6;
    pointer-events: none;
}

/* Animations */
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.fade-in {
    animation: fadeIn 0.3s ease-in;
}

Best Practices

  1. Organize by feature: Group related files together

  2. Use modules: Keep code modular and reusable

  3. Follow conventions: Consistent naming and structure

  4. Progressive enhancement: Work without JavaScript

  5. Optimize performance: Minimize and bundle assets

  6. Test components: Unit and integration tests

  7. Document APIs: Clear component documentation

  8. Handle errors: Graceful error handling

  9. Accessibility: ARIA labels and keyboard support

  10. Security: Validate inputs, use CSRF tokens

See Also

Quick Start Guide

Get up and running with Wheels CLI in minutes.

Prerequisites

  • CommandBox 5.0+

  • Java 8+

  • Database (MySQL, PostgreSQL, SQL Server, or H2)

Installation

Install CommandBox

# macOS/Linux
curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
echo "deb https://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a /etc/apt/sources.list.d/commandbox.list
sudo apt-get update && sudo apt-get install commandbox

# Windows (PowerShell as Admin)
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install commandbox

Install Wheels CLI

box install wheels-cli

Creating Your First Application

1. Generate Application

wheels new blog
cd blog

This creates a new Wheels application with:

  • Complete directory structure

  • Configuration files

  • Sample code

2. Configure Database

Edit /config/settings.cfm:

<cfset set(dataSourceName="blog_development")>

Or use H2 embedded database:

wheels new blog --setupH2

3. Start Server

box server start

Visit http://localhost:3000

Creating Your First Feature

Let's create a blog post feature:

1. Generate Scaffold

wheels scaffold post --properties="title:string,content:text,published:boolean"

This generates:

  • Model with validations

  • Controller with CRUD actions

  • Views for all actions

  • Database migration

  • Test files

2. Run Migration

wheels dbmigrate latest

3. Add Routes

Edit /config/routes.cfm:

<cfscript>
    // Add this line
    resources("posts");
</cfscript>

4. Reload Application

wheels reload

5. Test Your Feature

Visit http://localhost:3000/posts

You now have a fully functional blog post management system!

Development Workflow

File Watching

In a new terminal:

wheels watch

Now changes to .cfc and .cfm files trigger automatic reloads.

Running Tests

# Run all tests
wheels test run

# Watch mode
wheels test run --watch

# Specific tests
wheels test run tests/models/PostTest.cfc

Adding Relationships

Let's add comments to posts:

# Generate comment model
wheels generate model comment --properties="author:string,content:text,postId:integer" \
  --belongs-to="post"

# Update post model
wheels generate property post comments --has-many

# Generate comments controller
wheels generate controller comments --rest

# Run migration
wheels dbmigrate latest

Common Tasks

Adding Authentication

# Generate user model
wheels scaffold user --properties="email:string,password:string,admin:boolean"

# Generate session controller
wheels generate controller sessions new,create,delete

# Run migrations
wheels dbmigrate latest

Adding API Endpoints

# Generate API resource
wheels generate api-resource product --properties="name:string,price:decimal"

# Or convert existing to API
wheels generate controller api/posts --api

Working with Views

# Generate specific views
wheels generate view posts featured
wheels generate view users profile

# Add layouts
echo '<cfoutput><!DOCTYPE html>...</cfoutput>' > views/layout.cfm

Best Practices

1. Use Migrations

Always use migrations for database changes:

# Create tables
wheels dbmigrate create table products

# Add columns
wheels dbmigrate create column products featured

# Create indexes
wheels dbmigrate create blank add_index_to_products

2. Write Tests

Generate tests for your code:

# After creating a model
wheels generate test model post

# After creating a controller
wheels generate test controller posts

3. Use Environment Configuration

# Development
wheels reload development

# Testing
wheels reload testing

# Production
wheels reload production

4. Version Control

git init
git add .
git commit -m "Initial Wheels application"

Add to .gitignore:

/db/sql/
/logs/
/temp/
.env

Debugging

Check Logs

tail -f logs/wheels.log

Enable Debug Mode

In /config/settings.cfm:

<cfset set(showDebugInformation=true)>

Common Issues

Port already in use:

box server start port=3001

Database connection failed:

# Check datasource
box server info
box server show

Migration failed:

# Check status
wheels dbmigrate info

# Run specific migration
wheels dbmigrate exec 20240120000000

Next Steps

  1. Read the Guides:

  2. Explore Commands:

    • wheels --help

    • wheels generate --help

    • wheels dbmigrate --help

  3. Join the Community:

Example: Complete Blog Application

Here's a complete blog setup:

# Create application
wheels new myblog --setupH2
cd myblog

# Generate blog structure
wheels scaffold post title:string,slug:string,content:text,publishedAt:datetime
wheels scaffold author name:string,email:string,bio:text
wheels generate model comment author:string,email:string,content:text,postId:integer \
  --belongs-to=post

# Update associations
wheels generate property post authorId:integer --belongs-to=author
wheels generate property post comments --has-many
wheels generate property author posts --has-many

# Add routes
echo '<cfset resources("posts")>' >> config/routes.cfm
echo '<cfset resources("authors")>' >> config/routes.cfm

# Run migrations
wheels dbmigrate latest

# Start development
box server start
wheels watch

# Visit http://localhost:3000/posts

You now have a working blog with posts, authors, and comments!

wheels generate resource

Generate a complete RESTful resource with model, controller, views, and routes.

Synopsis

wheels generate resource [name] [properties] [options]
wheels g resource [name] [properties] [options]

Description

The wheels generate resource command creates a complete RESTful resource including model, controller with all CRUD actions, views, routes, and optionally database migrations and tests. It's a comprehensive generator that sets up everything needed for a functioning resource.

Arguments

Argument
Description
Default

name

Resource name (typically singular)

Required

properties

Property definitions (name:type)

Options

Option
Description
Default

--skip-model

Skip model generation

false

--skip-controller

Skip controller generation

false

--skip-views

Skip view generation

false

--skip-route

Skip route generation

false

--skip-migration

Skip migration generation

false

--skip-tests

Skip test generation

false

--api

Generate API-only resource

false

--namespace

Namespace for the resource

--parent

Parent resource for nesting

--force

Overwrite existing files

false

--help

Show help information

Examples

Basic Resource

wheels generate resource product name:string price:float description:text

Generates:

  • Model: /models/Product.cfc

  • Controller: /controllers/Products.cfc

  • Views: /views/products/ (index, show, new, edit, _form)

  • Route: resources("products") in /config/routes.cfm

  • Migration: /db/migrate/[timestamp]_create_products.cfc

  • Tests: /tests/models/ProductTest.cfc, /tests/controllers/ProductsTest.cfc

API Resource

wheels generate resource api/product name:string price:float --api

Generates:

  • Model: /models/Product.cfc

  • Controller: /controllers/api/Products.cfc (JSON responses only)

  • Route: resources(name="products", except="new,edit") in API namespace

  • Migration: /db/migrate/[timestamp]_create_products.cfc

  • Tests: API-focused test files

Nested Resource

wheels generate resource comment content:text approved:boolean --parent=post

Generates nested structure with proper associations and routing.

Generated Files

Model

/models/Product.cfc:

component extends="Model" {
    
    function init() {
        // Properties
        property(name="name", sql="name");
        property(name="price", sql="price");
        property(name="description", sql="description");
        
        // Validations
        validatesPresenceOf(properties="name,price");
        validatesNumericalityOf(property="price", greaterThan=0);
        validatesLengthOf(property="name", maximum=255);
        
        // Callbacks
        beforeSave("sanitizeInput");
    }
    
    private function sanitizeInput() {
        this.name = Trim(this.name);
        if (StructKeyExists(this, "description")) {
            this.description = Trim(this.description);
        }
    }
    
}

Controller

/controllers/Products.cfc:

component extends="Controller" {
    
    function init() {
        // Filters
        filters(through="findProduct", only="show,edit,update,delete");
    }
    
    function index() {
        products = model("Product").findAll(order="createdAt DESC");
    }
    
    function show() {
        // Product loaded by filter
    }
    
    function new() {
        product = model("Product").new();
    }
    
    function create() {
        product = model("Product").new(params.product);
        
        if (product.save()) {
            flashInsert(success="Product was created successfully.");
            redirectTo(route="product", key=product.id);
        } else {
            renderView(action="new");
        }
    }
    
    function edit() {
        // Product loaded by filter
    }
    
    function update() {
        if (product.update(params.product)) {
            flashInsert(success="Product was updated successfully.");
            redirectTo(route="product", key=product.id);
        } else {
            renderView(action="edit");
        }
    }
    
    function delete() {
        if (product.delete()) {
            flashInsert(success="Product was deleted successfully.");
        } else {
            flashInsert(error="Product could not be deleted.");
        }
        redirectTo(route="products");
    }
    
    // Filters
    private function findProduct() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            flashInsert(error="Product not found.");
            redirectTo(route="products");
        }
    }
    
}

Views

/views/products/index.cfm:

<h1>Products</h1>

<p>
    #linkTo(route="newProduct", text="New Product", class="btn btn-primary")#
</p>

<cfif products.recordCount>
    <table class="table table-striped">
        <thead>
            <tr>
                <th>Name</th>
                <th>Price</th>
                <th>Created</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <cfoutput query="products">
                <tr>
                    <td>#linkTo(route="product", key=products.id, text=products.name)#</td>
                    <td>#dollarFormat(products.price)#</td>
                    <td>#dateFormat(products.createdAt, "mmm dd, yyyy")#</td>
                    <td>
                        #linkTo(route="editProduct", key=products.id, text="Edit", class="btn btn-sm btn-secondary")#
                        #linkTo(route="product", key=products.id, text="Delete", method="delete", confirm="Are you sure?", class="btn btn-sm btn-danger")#
                    </td>
                </tr>
            </cfoutput>
        </tbody>
    </table>
<cfelse>
    <p class="alert alert-info">No products found. #linkTo(route="newProduct", text="Create one now")#!</p>
</cfif>

/views/products/_form.cfm:

#errorMessagesFor("product")#

#startFormTag(route=formRoute, key=formKey, class="needs-validation")#
    
    <div class="mb-3">
        #textField(objectName="product", property="name", label="Product Name", class="form-control", required=true)#
    </div>
    
    <div class="mb-3">
        #numberField(objectName="product", property="price", label="Price", class="form-control", step="0.01", min="0", required=true)#
    </div>
    
    <div class="mb-3">
        #textArea(objectName="product", property="description", label="Description", class="form-control", rows=5)#
    </div>
    
    <div class="mb-3">
        #submitTag(value=submitValue, class="btn btn-primary")#
        #linkTo(route="products", text="Cancel", class="btn btn-secondary")#
    </div>
    
#endFormTag()#

Migration

/db/migrate/[timestamp]_create_products.cfc:

component extends="wheels.migrator.Migration" hint="Create products table" {
    
    function up() {
        transaction {
            createTable(name="products", force=true) {
                t.increments("id");
                t.string("name", limit=255, null=false);
                t.decimal("price", precision=10, scale=2, null=false);
                t.text("description");
                t.timestamps();
                t.index("name");
            };
        }
    }
    
    function down() {
        transaction {
            dropTable("products");
        }
    }
    
}

Routes

Added to /config/routes.cfm:

<cfset resources("products")>

API Resource Generation

API Controller

/controllers/api/Products.cfc:

component extends="Controller" {
    
    function init() {
        provides("json");
        filters(through="findProduct", only="show,update,delete");
    }
    
    function index() {
        products = model("Product").findAll(
            order="createdAt DESC",
            page=params.page ?: 1,
            perPage=params.perPage ?: 25
        );
        
        renderWith({
            data: products,
            meta: {
                page: products.currentPage,
                totalPages: products.totalPages,
                totalRecords: products.totalRecords
            }
        });
    }
    
    function show() {
        renderWith(product);
    }
    
    function create() {
        product = model("Product").new(params.product);
        
        if (product.save()) {
            renderWith(data=product, status=201);
        } else {
            renderWith(
                data={errors: product.allErrors()},
                status=422
            );
        }
    }
    
    function update() {
        if (product.update(params.product)) {
            renderWith(product);
        } else {
            renderWith(
                data={errors: product.allErrors()},
                status=422
            );
        }
    }
    
    function delete() {
        if (product.delete()) {
            renderWith(data={message: "Product deleted successfully"});
        } else {
            renderWith(
                data={error: "Could not delete product"},
                status=400
            );
        }
    }
    
    private function findProduct() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            renderWith(
                data={error: "Product not found"},
                status=404
            );
        }
    }
    
}

Nested Resources

Generate Nested Resource

wheels generate resource review rating:integer comment:text --parent=product

Nested Model

Includes association:

component extends="Model" {
    
    function init() {
        belongsTo("product");
        
        property(name="rating", sql="rating");
        property(name="comment", sql="comment");
        property(name="productId", sql="product_id");
        
        validatesPresenceOf(properties="rating,comment,productId");
        validatesNumericalityOf(property="rating", greaterThanOrEqualTo=1, lessThanOrEqualTo=5);
    }
    
}

Nested Routes

<cfset resources("products")>
    <cfset resources("reviews")>
</cfset>

Property Types

Supported Types

Type
Database Type
Validation

string

VARCHAR(255)

Length validation

text

TEXT

None by default

integer

INT

Numerical validation

float

DECIMAL

Numerical validation

decimal

DECIMAL

Numerical validation

boolean

BOOLEAN

Boolean validation

date

DATE

Date format validation

datetime

DATETIME

DateTime validation

time

TIME

Time validation

binary

BLOB

None

Property Options

wheels generate resource user \
  email:string:required:unique \
  age:integer:min=18:max=120 \
  bio:text:limit=1000 \
  isActive:boolean:default=true

Advanced Options

Skip Components

# Generate only model and migration
wheels generate resource product name:string --skip-controller --skip-views --skip-route

# Generate only controller and views
wheels generate resource product --skip-model --skip-migration

Namespace Resources

wheels generate resource admin/product name:string --namespace=admin

Creates:

  • /controllers/admin/Products.cfc

  • /views/admin/products/

  • Namespaced routes

Custom Templates

wheels generate resource product name:string --template=custom

Testing

Generated Tests

Model Test (/tests/models/ProductTest.cfc):

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        model("Product").deleteAll();
    }
    
    function test_valid_product_saves() {
        product = model("Product").new(
            name="Test Product",
            price=19.99,
            description="Test description"
        );
        
        assert(product.save());
        assert(product.id > 0);
    }
    
    function test_requires_name() {
        product = model("Product").new(price=19.99);
        
        assert(!product.save());
        assert(ArrayLen(product.errorsOn("name")) > 0);
    }
    
    function test_requires_positive_price() {
        product = model("Product").new(name="Test", price=-10);
        
        assert(!product.save());
        assert(ArrayLen(product.errorsOn("price")) > 0);
    }
    
}

Controller Test (/tests/controllers/ProductsTest.cfc):

component extends="wheels.Test" {
    
    function test_index_returns_products() {
        products = createProducts(3);
        
        result = processRequest(route="products", method="GET");
        
        assert(result.status == 200);
        assert(Find("Products", result.body));
        assert(FindNoCase(products[1].name, result.body));
    }
    
    function test_create_valid_product() {
        params = {
            product: {
                name: "New Product",
                price: 29.99,
                description: "New product description"
            }
        };
        
        result = processRequest(route="products", method="POST", params=params);
        
        assert(result.status == 302);
        assert(model("Product").count() == 1);
    }
    
}

Best Practices

  1. Use singular names: product not products

  2. Define all properties: Include types and validations

  3. Add indexes: For frequently queried fields

  4. Include tests: Don't skip test generation

  5. Use namespaces: For admin or API resources

  6. Follow conventions: Stick to RESTful patterns

Common Patterns

Soft Delete Resource

wheels generate resource product name:string deletedAt:datetime:nullable

Publishable Resource

wheels generate resource post title:string content:text publishedAt:datetime:nullable status:string:default=draft

User-Owned Resource

wheels generate resource task title:string userId:integer:belongsTo=user completed:boolean:default=false

Hierarchical Resource

wheels generate resource category name:string parentId:integer:nullable:belongsTo=category

Customization

Custom Resource Templates

Create in ~/.wheels/templates/resources/:

custom-resource/
├── model.cfc
├── controller.cfc
├── views/
│   ├── index.cfm
│   ├── show.cfm
│   ├── new.cfm
│   ├── edit.cfm
│   └── _form.cfm
└── migration.cfc

Template Variables

Available in templates:

  • ${resourceName} - Singular name

  • ${resourceNamePlural} - Plural name

  • ${modelName} - Model class name

  • ${controllerName} - Controller class name

  • ${tableName} - Database table name

  • ${properties} - Array of property definitions

See Also

wheels generate test

Generate test files for models, controllers, views, and other components.

Synopsis

wheels generate test [type] [name] [options]
wheels g test [type] [name] [options]

Description

The wheels generate test command creates test files for various components of your Wheels application. It generates appropriate test scaffolding based on the component type and includes common test cases to get you started.

Arguments

Argument
Description
Default

type

Type of test (model, controller, view, helper, route)

Required

name

Name of the component to test

Required

Options

Option
Description
Default

--methods

Specific methods to test

All methods

--integration

Generate integration tests

false

--coverage

Include coverage setup

false

--fixtures

Generate test fixtures

true

--force

Overwrite existing files

false

--help

Show help information

Examples

Model Test

wheels generate test model product

Generates /tests/models/ProductTest.cfc:

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        // Clear test data
        model("Product").deleteAll();
        
        // Setup test fixtures
        variables.validProduct = {
            name: "Test Product",
            price: 19.99,
            description: "Test product description"
        };
    }
    
    function teardown() {
        super.teardown();
        // Clean up after tests
        model("Product").deleteAll();
    }
    
    // Validation Tests
    
    function test_valid_product_saves_successfully() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        
        // Act
        result = product.save();
        
        // Assert
        assert(result, "Product should save successfully");
        assert(product.id > 0, "Product should have an ID after saving");
    }
    
    function test_product_requires_name() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        product.name = "";
        
        // Act
        result = product.save();
        
        // Assert
        assert(!result, "Product should not save without name");
        assert(ArrayLen(product.errorsOn("name")) > 0, "Should have error on name");
    }
    
    function test_product_requires_positive_price() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        product.price = -10;
        
        // Act
        result = product.save();
        
        // Assert
        assert(!result, "Product should not save with negative price");
        assert(ArrayLen(product.errorsOn("price")) > 0, "Should have error on price");
    }
    
    function test_product_name_must_be_unique() {
        // Arrange
        product1 = model("Product").create(variables.validProduct);
        product2 = model("Product").new(variables.validProduct);
        
        // Act
        result = product2.save();
        
        // Assert
        assert(!result, "Should not save duplicate product name");
        assert(ArrayLen(product2.errorsOn("name")) > 0, "Should have uniqueness error");
    }
    
    // Association Tests
    
    function test_product_has_many_reviews() {
        // Arrange
        product = model("Product").create(variables.validProduct);
        review = product.createReview(rating=5, comment="Great product!");
        
        // Act
        reviews = product.reviews();
        
        // Assert
        assert(reviews.recordCount == 1, "Product should have one review");
        assert(reviews.rating == 5, "Review rating should be 5");
    }
    
    // Callback Tests
    
    function test_before_save_sanitizes_input() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        product.name = "  Test Product  ";
        
        // Act
        product.save();
        
        // Assert
        assert(product.name == "Test Product", "Name should be trimmed");
    }
    
    // Scope Tests
    
    function test_active_scope_returns_only_active_products() {
        // Arrange
        activeProduct = model("Product").create(
            variables.validProduct & {isActive: true}
        );
        inactiveProduct = model("Product").create(
            name="Inactive Product",
            price=29.99,
            isActive=false
        );
        
        // Act
        activeProducts = model("Product").active();
        
        // Assert
        assert(activeProducts.recordCount == 1, "Should have one active product");
        assert(activeProducts.id == activeProduct.id, "Should return active product");
    }
    
    // Method Tests
    
    function test_calculate_discount_price() {
        // Arrange
        product = model("Product").create(variables.validProduct);
        
        // Act
        discountPrice = product.calculateDiscountPrice(0.20); // 20% discount
        
        // Assert
        expected = product.price * 0.80;
        assert(discountPrice == expected, "Discount price should be 80% of original");
    }
    
    // Integration Tests
    
    function test_product_lifecycle() {
        transaction {
            // Create
            product = model("Product").new(variables.validProduct);
            assert(product.save(), "Should create product");
            productId = product.id;
            
            // Read
            foundProduct = model("Product").findByKey(productId);
            assert(IsObject(foundProduct), "Should find product");
            assert(foundProduct.name == variables.validProduct.name, "Should have correct name");
            
            // Update
            foundProduct.price = 24.99;
            assert(foundProduct.save(), "Should update product");
            
            // Verify update
            updatedProduct = model("Product").findByKey(productId);
            assert(updatedProduct.price == 24.99, "Price should be updated");
            
            // Delete
            assert(updatedProduct.delete(), "Should delete product");
            
            // Verify deletion
            deletedProduct = model("Product").findByKey(productId);
            assert(!IsObject(deletedProduct), "Product should not exist");
            
            // Rollback transaction
            transaction action="rollback";
        }
    }
    
}

Controller Test

wheels generate test controller products

Generates /tests/controllers/ProductsTest.cfc:

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        // Setup test data
        model("Product").deleteAll();
        
        variables.testProducts = [];
        for (i = 1; i <= 3; i++) {
            ArrayAppend(variables.testProducts, 
                model("Product").create(
                    name="Product #i#",
                    price=19.99 * i,
                    description="Description #i#"
                )
            );
        }
    }
    
    function teardown() {
        super.teardown();
        model("Product").deleteAll();
    }
    
    // Action Tests
    
    function test_index_returns_all_products() {
        // Act
        result = processRequest(route="products", method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find("<h1>Products</h1>", result.body), "Should have products heading");
        
        for (product in variables.testProducts) {
            assert(Find(product.name, result.body), "Should display product: #product.name#");
        }
    }
    
    function test_show_displays_product_details() {
        // Arrange
        product = variables.testProducts[1];
        
        // Act
        result = processRequest(route="product", key=product.id, method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find(product.name, result.body), "Should display product name");
        assert(Find(DollarFormat(product.price), result.body), "Should display formatted price");
    }
    
    function test_show_returns_404_for_invalid_product() {
        // Act
        result = processRequest(route="product", key=99999, method="GET");
        
        // Assert
        assert(result.status == 302, "Should redirect");
        assert(result.flash.error == "Product not found.", "Should have error message");
    }
    
    function test_new_displays_form() {
        // Act
        result = processRequest(route="newProduct", method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find("<form", result.body), "Should have form");
        assert(Find('name="product[name]"', result.body), "Should have name field");
        assert(Find('name="product[price]"', result.body), "Should have price field");
    }
    
    function test_create_with_valid_data() {
        // Arrange
        params = {
            product: {
                name: "New Test Product",
                price: 39.99,
                description: "New product description"
            }
        };
        
        // Act
        result = processRequest(route="products", method="POST", params=params);
        
        // Assert
        assert(result.status == 302, "Should redirect after creation");
        assert(result.flash.success == "Product was created successfully.", "Should have success message");
        
        // Verify product was created
        newProduct = model("Product").findOne(where="name='New Test Product'");
        assert(IsObject(newProduct), "Product should be created");
        assert(newProduct.price == 39.99, "Should have correct price");
    }
    
    function test_create_with_invalid_data() {
        // Arrange
        params = {
            product: {
                name: "",
                price: -10,
                description: "Invalid product"
            }
        };
        
        // Act
        result = processRequest(route="products", method="POST", params=params);
        
        // Assert
        assert(result.status == 200, "Should render form again");
        assert(Find("error", result.body), "Should display errors");
        assert(model("Product").count(where="description='Invalid product'") == 0, 
               "Should not create invalid product");
    }
    
    function test_edit_displays_form_with_product_data() {
        // Arrange
        product = variables.testProducts[1];
        
        // Act
        result = processRequest(route="editProduct", key=product.id, method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find('value="#product.name#"', result.body), "Should pre-fill name");
        assert(Find(ToString(product.price), result.body), "Should pre-fill price");
    }
    
    function test_update_with_valid_data() {
        // Arrange
        product = variables.testProducts[1];
        params = {
            product: {
                name: "Updated Product Name",
                price: 49.99
            }
        };
        
        // Act
        result = processRequest(route="product", key=product.id, method="PUT", params=params);
        
        // Assert
        assert(result.status == 302, "Should redirect after update");
        assert(result.flash.success == "Product was updated successfully.", "Should have success message");
        
        // Verify update
        updatedProduct = model("Product").findByKey(product.id);
        assert(updatedProduct.name == "Updated Product Name", "Name should be updated");
        assert(updatedProduct.price == 49.99, "Price should be updated");
    }
    
    function test_delete_removes_product() {
        // Arrange
        product = variables.testProducts[1];
        initialCount = model("Product").count();
        
        // Act
        result = processRequest(route="product", key=product.id, method="DELETE");
        
        // Assert
        assert(result.status == 302, "Should redirect after deletion");
        assert(result.flash.success == "Product was deleted successfully.", "Should have success message");
        assert(model("Product").count() == initialCount - 1, "Should have one less product");
        assert(!IsObject(model("Product").findByKey(product.id)), "Product should be deleted");
    }
    
    // Filter Tests
    
    function test_authentication_required_for_protected_actions() {
        // Test that certain actions require authentication
        protectedRoutes = [
            {route: "newProduct", method: "GET"},
            {route: "products", method: "POST"},
            {route: "editProduct", key: variables.testProducts[1].id, method: "GET"},
            {route: "product", key: variables.testProducts[1].id, method: "PUT"},
            {route: "product", key: variables.testProducts[1].id, method: "DELETE"}
        ];
        
        for (route in protectedRoutes) {
            // Act without authentication
            result = processRequest(argumentCollection=route);
            
            // Assert
            assert(result.status == 302, "Should redirect unauthenticated user");
            assert(result.redirectUrl contains "login", "Should redirect to login");
        }
    }
    
    // Helper method for processing requests
    private function processRequest(
        required string route,
        string method = "GET",
        struct params = {},
        numeric key = 0
    ) {
        local.args = {
            route: arguments.route,
            method: arguments.method,
            params: arguments.params
        };
        
        if (arguments.key > 0) {
            local.args.key = arguments.key;
        }
        
        return $processRequest(argumentCollection=local.args);
    }
    
}

View Test

wheels generate test view products/index

Generates /tests/views/products/IndexTest.cfc:

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        // Create test data
        variables.products = QueryNew(
            "id,name,price,createdAt",
            "integer,varchar,decimal,timestamp"
        );
        
        for (i = 1; i <= 3; i++) {
            QueryAddRow(variables.products, {
                id: i,
                name: "Product #i#",
                price: 19.99 * i,
                createdAt: Now()
            });
        }
    }
    
    function test_index_view_renders_product_list() {
        // Act
        result = $renderView(
            view="/products/index",
            products=variables.products,
            layout=false
        );
        
        // Assert
        assert(Find("<h1>Products</h1>", result), "Should have products heading");
        assert(Find("<table", result), "Should have products table");
        assert(Find("Product 1", result), "Should display first product");
        assert(Find("Product 2", result), "Should display second product");
        assert(Find("Product 3", result), "Should display third product");
    }
    
    function test_index_view_shows_empty_state() {
        // Arrange
        emptyQuery = QueryNew("id,name,price,createdAt");
        
        // Act
        result = $renderView(
            view="/products/index",
            products=emptyQuery,
            layout=false
        );
        
        // Assert
        assert(Find("No products found", result), "Should show empty state message");
        assert(Find("Create one now", result), "Should have create link");
        assert(!Find("<table", result), "Should not show table when empty");
    }
    
    function test_index_view_formats_prices_correctly() {
        // Act
        result = $renderView(
            view="/products/index",
            products=variables.products,
            layout=false
        );
        
        // Assert
        assert(Find("$19.99", result), "Should format first price");
        assert(Find("$39.98", result), "Should format second price");
        assert(Find("$59.97", result), "Should format third price");
    }
    
    function test_index_view_includes_action_links() {
        // Act
        result = $renderView(
            view="/products/index",
            products=variables.products,
            layout=false
        );
        
        // Assert
        assert(Find("New Product", result), "Should have new product link");
        assert(FindNoCase("href=""/products/new""", result), "New link should be correct");
        
        // Check action links for each product
        for (row in variables.products) {
            assert(Find("View</a>", result), "Should have view link");
            assert(Find("Edit</a>", result), "Should have edit link");
            assert(Find("Delete</a>", result), "Should have delete link");
        }
    }
    
    function test_index_view_with_pagination() {
        // Arrange
        paginatedProducts = Duplicate(variables.products);
        paginatedProducts.currentPage = 2;
        paginatedProducts.totalPages = 5;
        paginatedProducts.totalRecords = 50;
        
        // Act
        result = $renderView(
            view="/products/index",
            products=paginatedProducts,
            layout=false
        );
        
        // Assert
        assert(Find("class=""pagination""", result), "Should have pagination");
        assert(Find("Previous", result), "Should have previous link");
        assert(Find("Next", result), "Should have next link");
        assert(Find("Page 2 of 5", result), "Should show current page");
    }
    
    function test_index_view_escapes_html() {
        // Arrange
        productsWithHtml = QueryNew("id,name,price,createdAt");
        QueryAddRow(productsWithHtml, {
            id: 1,
            name: "<script>alert('XSS')</script>",
            price: 19.99,
            createdAt: Now()
        });
        
        // Act
        result = $renderView(
            view="/products/index",
            products=productsWithHtml,
            layout=false
        );
        
        // Assert
        assert(!Find("<script>alert('XSS')</script>", result), 
               "Should not have unescaped script tag");
        assert(Find("&lt;script&gt;", result), "Should have escaped HTML");
    }
    
}

Integration Test

wheels generate test controller products --integration

Generates additional integration tests:

component extends="wheels.Test" {
    
    function test_complete_product_workflow() {
        transaction {
            // 1. View product list (empty)
            result = $visit(route="products");
            assert(result.status == 200);
            assert(Find("No products found", result.body));
            
            // 2. Navigate to new product form
            result = $click("Create one now");
            assert(result.status == 200);
            assert(Find("<form", result.body));
            
            // 3. Submit new product form
            result = $submitForm({
                "product[name]": "Integration Test Product",
                "product[price]": "29.99",
                "product[description]": "Test description"
            });
            assert(result.status == 302);
            assert(result.flash.success);
            
            // 4. View created product
            product = model("Product").findOne(order="id DESC");
            result = $visit(route="product", key=product.id);
            assert(result.status == 200);
            assert(Find("Integration Test Product", result.body));
            
            // 5. Edit product
            result = $click("Edit");
            assert(Find('value="Integration Test Product"', result.body));
            
            result = $submitForm({
                "product[name]": "Updated Product",
                "product[price]": "39.99"
            });
            assert(result.status == 302);
            
            // 6. Verify update
            result = $visit(route="product", key=product.id);
            assert(Find("Updated Product", result.body));
            assert(Find("$39.99", result.body));
            
            // 7. Delete product
            result = $click("Delete", confirm=true);
            assert(result.status == 302);
            assert(result.flash.success contains "deleted");
            
            // 8. Verify deletion
            assert(!IsObject(model("Product").findByKey(product.id)));
            
            transaction action="rollback";
        }
    }
    
}

Test Types

Model Tests

Focus on:

  • Validations

  • Associations

  • Callbacks

  • Scopes

  • Custom methods

  • Data integrity

Controller Tests

Focus on:

  • Action responses

  • Parameter handling

  • Authentication/authorization

  • Flash messages

  • Redirects

  • Error handling

View Tests

Focus on:

  • Content rendering

  • Data display

  • HTML structure

  • Escaping/security

  • Conditional display

  • Helpers usage

Helper Tests

wheels generate test helper format
component extends="wheels.Test" {
    
    function test_format_currency() {
        assert(formatCurrency(19.99) == "$19.99");
        assert(formatCurrency(1000) == "$1,000.00");
        assert(formatCurrency(0) == "$0.00");
        assert(formatCurrency(-50.5) == "-$50.50");
    }
    
}

Route Tests

wheels generate test route products
component extends="wheels.Test" {
    
    function test_products_routes() {
        // Test route resolution
        assert($resolveRoute("/products") == {controller: "products", action: "index"});
        assert($resolveRoute("/products/new") == {controller: "products", action: "new"});
        assert($resolveRoute("/products/123") == {controller: "products", action: "show", key: "123"});
        
        // Test route generation
        assert(urlFor(route="products") == "/products");
        assert(urlFor(route="product", key=123) == "/products/123");
        assert(urlFor(route="newProduct") == "/products/new");
    }
    
}

Test Fixtures

Generate Fixtures

wheels generate test model product --fixtures

Creates /tests/fixtures/products.cfc:

component {
    
    function load() {
        // Clear existing data
        model("Product").deleteAll();
        
        // Load fixture data
        fixtures = [
            {
                name: "Widget",
                price: 19.99,
                description: "Standard widget",
                categoryId: 1,
                isActive: true
            },
            {
                name: "Gadget",
                price: 29.99,
                description: "Premium gadget",
                categoryId: 2,
                isActive: true
            },
            {
                name: "Doohickey",
                price: 9.99,
                description: "Budget doohickey",
                categoryId: 1,
                isActive: false
            }
        ];
        
        for (fixture in fixtures) {
            model("Product").create(fixture);
        }
        
        return fixtures;
    }
    
    function loadWithAssociations() {
        products = load();
        
        // Add reviews
        model("Review").create(
            productId: products[1].id,
            rating: 5,
            comment: "Excellent product!"
        );
        
        return products;
    }
    
}

Test Helpers

Custom Assertions

// In test file
function assertProductValid(required any product) {
    assert(IsObject(arguments.product), "Product should be an object");
    assert(arguments.product.id > 0, "Product should have valid ID");
    assert(Len(arguments.product.name), "Product should have name");
    assert(arguments.product.price > 0, "Product should have positive price");
}

function assertHasError(required any model, required string property) {
    local.errors = arguments.model.errorsOn(arguments.property);
    assert(ArrayLen(local.errors) > 0, 
           "Expected error on #arguments.property# but found none");
}

Test Data Builders

function createTestProduct(struct overrides = {}) {
    local.defaults = {
        name: "Test Product #CreateUUID()#",
        price: RandRange(10, 100) + (RandRange(0, 99) / 100),
        description: "Test description",
        isActive: true
    };
    
    StructAppend(local.defaults, arguments.overrides, true);
    
    return model("Product").create(local.defaults);
}

function createTestUser(struct overrides = {}) {
    local.defaults = {
        email: "test-#CreateUUID()#@example.com",
        password: "password123",
        firstName: "Test",
        lastName: "User"
    };
    
    StructAppend(local.defaults, arguments.overrides, true);
    
    return model("User").create(local.defaults);
}

Running Tests

Run all tests

wheels test

Run specific test file

wheels test app tests/models/ProductTest.cfc

Run specific test method

wheels test app tests/models/ProductTest.cfc::test_product_requires_name

Run with coverage

wheels test --coverage

Best Practices

  1. Test in isolation: Each test should be independent

  2. Use descriptive names: Test names should explain what they test

  3. Follow AAA pattern: Arrange, Act, Assert

  4. Clean up data: Use setup/teardown or transactions

  5. Test edge cases: Empty data, nulls, extremes

  6. Mock external services: Don't rely on external APIs

  7. Keep tests fast: Optimize slow tests

  8. Test one thing: Each test should verify one behavior

  9. Use fixtures wisely: Share common test data

  10. Run tests frequently: Before commits and in CI

Common Testing Patterns

Testing Private Methods

function test_private_method_through_public_interface() {
    // Don't test private methods directly
    // Test them through public methods that use them
    product = model("Product").new(name: "  Test  ");
    product.save(); // Calls private sanitize method
    assert(product.name == "Test");
}

Testing Time-Dependent Code

function test_expiration_date() {
    // Use specific dates instead of Now()
    testDate = CreateDate(2024, 1, 1);
    product = model("Product").new(
        expiresAt: DateAdd("d", 30, testDate)
    );
    
    // Test with mocked current date
    request.currentDate = testDate;
    assert(!product.isExpired());
    
    request.currentDate = DateAdd("d", 31, testDate);
    assert(product.isExpired());
}

Testing Randomness

function test_random_discount() {
    // Test the range, not specific values
    product = model("Product").new(price: 100);
    
    for (i = 1; i <= 100; i++) {
        discount = product.getRandomDiscount();
        assert(discount >= 0.05 && discount <= 0.25, 
               "Discount should be between 5% and 25%");
    }
}

See Also

wheels generate snippets

Generate code snippets and boilerplate code for common patterns.

Synopsis

wheels generate snippets [pattern] [options]
wheels g snippets [pattern] [options]

Description

The wheels generate snippets command creates code snippets for common Wheels patterns and best practices. It provides ready-to-use code blocks that can be customized for your specific needs, helping you implement standard patterns quickly and consistently.

Arguments

Argument
Description
Default

pattern

Snippet pattern to generate

Shows available patterns

Options

Option
Description
Default

--list

List all available snippets

false

--category

Filter by category

All categories

--output

Output format (console, file, clipboard)

console

--customize

Interactive customization

false

--force

Overwrite existing files

false

--help

Show help information

Available Snippets

List All Snippets

wheels generate snippets --list

Output:

Available Snippets:
━━━━━━━━━━━━━━━━━━━

Authentication:
  - login-form          Login form with remember me
  - auth-filter         Authentication filter
  - password-reset      Password reset flow
  - user-registration   User registration with validation

Model Patterns:
  - soft-delete         Soft delete implementation
  - audit-trail         Audit trail with timestamps
  - sluggable          URL-friendly slugs
  - versionable        Version tracking
  - searchable         Full-text search

Controller Patterns:
  - crud-actions       Complete CRUD actions
  - api-controller     JSON API controller
  - nested-resource    Nested resource controller
  - admin-controller   Admin area controller

View Patterns:
  - form-with-errors   Form with error handling
  - pagination-links   Pagination navigation
  - search-form        Search form with filters
  - ajax-form          AJAX form submission

Database:
  - migration-indexes  Common index patterns
  - seed-data         Database seeding
  - constraints       Foreign key constraints

Authentication Snippets

Login Form

wheels generate snippets login-form

Generates:

<!--- views/sessions/new.cfm --->
<h1>Login</h1>

#errorMessagesFor("user")#

#startFormTag(action="create", class="login-form")#
    
    <div class="form-group">
        #textField(
            objectName="user",
            property="email",
            label="Email",
            class="form-control",
            placeholder="email@example.com",
            required=true,
            autofocus=true
        )#
    </div>
    
    <div class="form-group">
        #passwordField(
            objectName="user",
            property="password",
            label="Password",
            class="form-control",
            required=true
        )#
    </div>
    
    <div class="form-group">
        #checkBox(
            objectName="user",
            property="rememberMe",
            label="Remember me",
            value="1"
        )#
    </div>
    
    <div class="form-group">
        #submitTag(value="Login", class="btn btn-primary")#
        #linkTo(text="Forgot password?", route="forgotPassword", class="btn btn-link")#
    </div>
    
#endFormTag()#

<!--- controllers/Sessions.cfc --->
component extends="Controller" {
    
    function new() {
        user = model("User").new();
    }
    
    function create() {
        user = model("User").findOne(where="email='#params.user.email#'");
        
        if (IsObject(user) && user.authenticate(params.user.password)) {
            session.userId = user.id;
            
            if (params.user.rememberMe == 1) {
                cookie.rememberToken = user.generateRememberToken();
                cookie.userId = encrypt(user.id, application.encryptionKey);
            }
            
            flashInsert(success="Welcome back, #user.firstName#!");
            redirectTo(route="dashboard");
        } else {
            user = model("User").new(email=params.user.email);
            flashInsert(error="Invalid email or password.");
            renderView(action="new");
        }
    }
    
    function delete() {
        StructDelete(session, "userId");
        StructDelete(cookie, "rememberToken");
        StructDelete(cookie, "userId");
        
        flashInsert(success="You have been logged out.");
        redirectTo(route="home");
    }
    
}

Authentication Filter

wheels generate snippets auth-filter

Generates:

// In Controller.cfc or specific controller

function init() {
    filters(through="authenticate", except="new,create");
    filters(through="rememberUser", only="new");
}

private function authenticate() {
    if (!isLoggedIn()) {
        storeLocation();
        flashInsert(notice="Please log in to continue.");
        redirectTo(route="login");
    }
}

private function isLoggedIn() {
    return StructKeyExists(session, "userId") && session.userId > 0;
}

private function currentUser() {
    if (!StructKeyExists(request, "currentUser")) {
        if (isLoggedIn()) {
            request.currentUser = model("User").findByKey(session.userId);
        } else {
            request.currentUser = false;
        }
    }
    return request.currentUser;
}

private function rememberUser() {
    if (!isLoggedIn() && StructKeyExists(cookie, "rememberToken")) {
        user = model("User").findOne(
            where="rememberToken='#cookie.rememberToken#' AND rememberTokenExpiresAt > NOW()"
        );
        
        if (IsObject(user)) {
            session.userId = user.id;
            user.updateRememberToken();
        }
    }
}

private function storeLocation() {
    if (request.method == "GET") {
        session.returnTo = request.cgi.path_info;
        if (Len(request.cgi.query_string)) {
            session.returnTo &= "?" & request.cgi.query_string;
        }
    }
}

private function redirectBackOrTo(required string route) {
    if (StructKeyExists(session, "returnTo")) {
        local.returnTo = session.returnTo;
        StructDelete(session, "returnTo");
        redirectTo(location=local.returnTo);
    } else {
        redirectTo(route=arguments.route);
    }
}

Model Patterns

Soft Delete

wheels generate snippets soft-delete

Generates:

// In Model init()
property(name="deletedAt", sql="deleted_at");

// Soft delete callbacks
beforeDelete("softDelete");
afterFind("excludeDeleted");

// Default scope
function excludeDeleted() {
    if (!StructKeyExists(arguments, "includeSoftDeleted") || !arguments.includeSoftDeleted) {
        if (StructKeyExists(this, "deletedAt") && !IsNull(this.deletedAt)) {
            return false; // Exclude from results
        }
    }
}

// Soft delete implementation
private function softDelete() {
    this.deletedAt = Now();
    this.save(validate=false, callbacks=false);
    return false; // Prevent actual deletion
}

// Scopes
function active() {
    return this.findAll(where="deleted_at IS NULL", argumentCollection=arguments);
}

function deleted() {
    return this.findAll(where="deleted_at IS NOT NULL", argumentCollection=arguments);
}

function withDeleted() {
    return this.findAll(includeSoftDeleted=true, argumentCollection=arguments);
}

// Restore method
function restore() {
    this.deletedAt = "";
    return this.save(validate=false);
}

// Permanent delete
function forceDelete() {
    return this.delete(callbacks=false);
}

Audit Trail

wheels generate snippets audit-trail --customize

Interactive customization:

? Include user tracking? (Y/n) › Y
? Track IP address? (y/N) › Y
? Track changes in JSON? (Y/n) › Y

Generates:

// models/AuditLog.cfc
component extends="Model" {
    
    function init() {
        belongsTo("user");
        
        property(name="modelName", sql="model_name");
        property(name="recordId", sql="record_id");
        property(name="action", sql="action");
        property(name="changes", sql="changes");
        property(name="userId", sql="user_id");
        property(name="ipAddress", sql="ip_address");
        property(name="userAgent", sql="user_agent");
        
        validatesPresenceOf("modelName,recordId,action");
    }
    
}

// In audited model
function init() {
    afterCreate("logCreate");
    afterUpdate("logUpdate");
    afterDelete("logDelete");
}

private function logCreate() {
    createAuditLog("create", this.properties());
}

private function logUpdate() {
    if (hasChanged()) {
        createAuditLog("update", this.changedProperties());
    }
}

private function logDelete() {
    createAuditLog("delete", {id: this.id});
}

private function createAuditLog(required string action, required struct data) {
    model("AuditLog").create({
        modelName: ListLast(GetMetaData(this).name, "."),
        recordId: this.id,
        action: arguments.action,
        changes: SerializeJSON(arguments.data),
        userId: request.currentUser.id ?: "",
        ipAddress: request.remoteAddress,
        userAgent: request.userAgent
    });
}

private function changedProperties() {
    local.changes = {};
    local.properties = this.properties();
    
    for (local.key in local.properties) {
        if (hasChanged(local.key)) {
            local.changes[local.key] = {
                from: this.changedFrom(local.key),
                to: local.properties[local.key]
            };
        }
    }
    
    return local.changes;
}

// Audit log migration
component extends="wheels.migrator.Migration" {
    
    function up() {
        createTable(name="audit_logs") {
            t.increments("id");
            t.string("model_name", null=false);
            t.integer("record_id", null=false);
            t.string("action", null=false);
            t.text("changes");
            t.integer("user_id");
            t.string("ip_address");
            t.string("user_agent");
            t.timestamps();
            
            t.index(["model_name", "record_id"]);
            t.index("user_id");
            t.index("created_at");
        };
    }
    
    function down() {
        dropTable("audit_logs");
    }
    
}

Controller Patterns

CRUD Actions

wheels generate snippets crud-actions

Generates complete CRUD controller with error handling, pagination, and filters.

API Controller

wheels generate snippets api-controller

Generates:

component extends="Controller" {
    
    function init() {
        provides("json");
        filters(through="setApiHeaders");
        filters(through="authenticateApi");
        filters(through="logApiRequest", except="index,show");
    }
    
    private function setApiHeaders() {
        header name="X-API-Version" value="1.0";
        header name="X-RateLimit-Limit" value="1000";
        header name="X-RateLimit-Remaining" value=getRateLimitRemaining();
    }
    
    private function authenticateApi() {
        local.token = getAuthToken();
        
        if (!Len(local.token)) {
            renderUnauthorized("Missing authentication token");
        }
        
        request.apiUser = model("ApiKey").authenticate(local.token);
        
        if (!IsObject(request.apiUser)) {
            renderUnauthorized("Invalid authentication token");
        }
    }
    
    private function getAuthToken() {
        // Check Authorization header
        if (StructKeyExists(getHttpRequestData().headers, "Authorization")) {
            local.auth = getHttpRequestData().headers.Authorization;
            if (Left(local.auth, 7) == "Bearer ") {
                return Mid(local.auth, 8, Len(local.auth));
            }
        }
        
        // Check X-API-Key header
        if (StructKeyExists(getHttpRequestData().headers, "X-API-Key")) {
            return getHttpRequestData().headers["X-API-Key"];
        }
        
        // Check query parameter
        if (StructKeyExists(params, "api_key")) {
            return params.api_key;
        }
        
        return "";
    }
    
    private function renderUnauthorized(required string message) {
        renderWith(
            data={
                error: {
                    code: 401,
                    message: arguments.message
                }
            },
            status=401
        );
    }
    
    private function renderError(required string message, numeric status = 400) {
        renderWith(
            data={
                error: {
                    code: arguments.status,
                    message: arguments.message
                }
            },
            status=arguments.status
        );
    }
    
    private function renderSuccess(required any data, numeric status = 200) {
        renderWith(
            data={
                success: true,
                data: arguments.data
            },
            status=arguments.status
        );
    }
    
    private function renderPaginated(required any query) {
        renderWith(
            data={
                success: true,
                data: arguments.query,
                pagination: {
                    page: arguments.query.currentPage,
                    perPage: arguments.query.perPage,
                    total: arguments.query.totalRecords,
                    pages: arguments.query.totalPages
                }
            }
        );
    }
    
}

View Patterns

Form with Errors

wheels generate snippets form-with-errors

AJAX Form

wheels generate snippets ajax-form

Generates:

<!--- View file --->
<div id="contact-form-container">
    #startFormTag(
        action="send",
        id="contact-form",
        class="ajax-form",
        data={
            remote: true,
            method: "post",
            success: "handleFormSuccess",
            error: "handleFormError"
        }
    )#
        
        <div class="form-messages" style="display: none;"></div>
        
        <div class="form-group">
            #textField(
                name="name",
                label="Name",
                class="form-control",
                required=true
            )#
        </div>
        
        <div class="form-group">
            #emailField(
                name="email",
                label="Email",
                class="form-control",
                required=true
            )#
        </div>
        
        <div class="form-group">
            #textArea(
                name="message",
                label="Message",
                class="form-control",
                rows=5,
                required=true
            )#
        </div>
        
        <div class="form-group">
            #submitTag(
                value="Send Message",
                class="btn btn-primary",
                data={loading: "Sending..."}
            )#
        </div>
        
    #endFormTag()#
</div>

<script>
// AJAX form handler
document.addEventListener('DOMContentLoaded', function() {
    const form = document.getElementById('contact-form');
    
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        
        const submitBtn = form.querySelector('[type="submit"]');
        const originalText = submitBtn.value;
        const loadingText = submitBtn.dataset.loading;
        
        // Disable form
        submitBtn.disabled = true;
        submitBtn.value = loadingText;
        
        // Send AJAX request
        fetch(form.action, {
            method: form.method,
            body: new FormData(form),
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                handleFormSuccess(data);
            } else {
                handleFormError(data);
            }
        })
        .catch(error => {
            handleFormError({message: 'Network error. Please try again.'});
        })
        .finally(() => {
            submitBtn.disabled = false;
            submitBtn.value = originalText;
        });
    });
});

function handleFormSuccess(data) {
    const form = document.getElementById('contact-form');
    const messages = form.querySelector('.form-messages');
    
    // Show success message
    messages.className = 'form-messages alert alert-success';
    messages.textContent = data.message || 'Message sent successfully!';
    messages.style.display = 'block';
    
    // Reset form
    form.reset();
    
    // Hide message after 5 seconds
    setTimeout(() => {
        messages.style.display = 'none';
    }, 5000);
}

function handleFormError(data) {
    const form = document.getElementById('contact-form');
    const messages = form.querySelector('.form-messages');
    
    // Show error message
    messages.className = 'form-messages alert alert-danger';
    messages.textContent = data.message || 'An error occurred. Please try again.';
    messages.style.display = 'block';
    
    // Show field errors
    if (data.errors) {
        Object.keys(data.errors).forEach(field => {
            const input = form.querySelector(`[name="${field}"]`);
            if (input) {
                input.classList.add('is-invalid');
                const error = document.createElement('div');
                error.className = 'invalid-feedback';
                error.textContent = data.errors[field].join(', ');
                input.parentNode.appendChild(error);
            }
        });
    }
}
</script>

<!--- Controller action --->
function send() {
    contact = model("Contact").new(params);
    
    if (contact.save()) {
        if (isAjax()) {
            renderWith(data={
                success: true,
                message: "Thank you! We'll be in touch soon."
            });
        } else {
            flashInsert(success="Thank you! We'll be in touch soon.");
            redirectTo(route="home");
        }
    } else {
        if (isAjax()) {
            renderWith(data={
                success: false,
                message: "Please correct the errors below.",
                errors: contact.allErrors()
            }, status=422);
        } else {
            renderView(action="new");
        }
    }
}

Database Snippets

Migration Indexes

wheels generate snippets migration-indexes

Generates common index patterns:

// Performance indexes
t.index("email"); // Single column
t.index(["last_name", "first_name"]); // Composite
t.index("created_at"); // Timestamp queries

// Unique constraints
t.index("email", unique=true);
t.index(["user_id", "role_id"], unique=true);

// Foreign key indexes
t.index("user_id");
t.index("category_id");

// Full-text search
t.index("title", type="fulltext");
t.index(["title", "content"], type="fulltext");

// Partial indexes (PostgreSQL)
t.index("email", where="deleted_at IS NULL");

// Expression indexes
t.index("LOWER(email)", name="idx_email_lower");

Seed Data

wheels generate snippets seed-data

Custom Snippets

Create Custom Snippet

wheels generate snippets --create=my-pattern

Creates template in ~/.wheels/snippets/my-pattern/:

my-pattern/
├── snippet.json
├── files/
│   ├── controller.cfc
│   ├── model.cfc
│   └── view.cfm
└── README.md

snippet.json:

{
    "name": "my-pattern",
    "description": "Custom pattern description",
    "category": "custom",
    "author": "Your Name",
    "version": "1.0.0",
    "variables": [
        {
            "name": "modelName",
            "prompt": "Model name?",
            "default": "MyModel"
        }
    ],
    "files": [
        {
            "source": "files/controller.cfc",
            "destination": "controllers/${controllerName}.cfc"
        }
    ]
}

Output Options

Copy to Clipboard

wheels generate snippets login-form --output=clipboard

Save to File

wheels generate snippets api-controller --output=file --path=./controllers/Api.cfc

Interactive Mode

wheels generate snippets --customize

Best Practices

  1. Review generated code: Customize for your needs

  2. Understand the patterns: Don't blindly copy

  3. Keep snippets updated: Maintain with framework updates

  4. Share useful patterns: Contribute back to community

  5. Document customizations: Note changes made

  6. Test generated code: Ensure it works in your context

  7. Use consistent patterns: Across your application

See Also

wheels dbmigrate info

Display database migration status and information.

Synopsis

wheels dbmigrate info

Description

The wheels dbmigrate info command shows the current state of database migrations, including which migrations have been run, which are pending, and the current database version.

Options

Option
Description

--help

Show help information

Output

The command displays:

  1. Current Version: The latest migration that has been run

  2. Available Migrations: All migration files found

  3. Migration Status: Which migrations are completed vs pending

  4. Database Details: Connection information

Example Output

╔═══════════════════════════════════════════════╗
║         Database Migration Status             ║
╚═══════════════════════════════════════════════╝

Current Version: 20240115120000
Database: myapp_development
Connection: MySQL 8.0

╔═══════════════════════════════════════════════╗
║              Migration History                ║
╚═══════════════════════════════════════════════╝

✓ 20240101100000_create_users_table.cfc
✓ 20240105150000_create_products_table.cfc
✓ 20240110090000_add_email_to_users.cfc
✓ 20240115120000_create_orders_table.cfc
○ 20240120140000_add_status_to_orders.cfc (pending)
○ 20240125160000_create_categories_table.cfc (pending)

Status: 4 completed, 2 pending

To run pending migrations, use: wheels dbmigrate latest

Migration Files Location

Migrations are stored in /db/migrate/ and follow the naming convention:

[timestamp]_[description].cfc

Example:

20240125160000_create_users_table.cfc

Understanding Version Numbers

  • Version numbers are timestamps in format: YYYYMMDDHHmmss

  • Higher numbers are newer migrations

  • Migrations run in chronological order

Database Schema Table

Migration status is tracked in schema_migrations table:

SELECT * FROM schema_migrations;
+----------------+
| version        |
+----------------+
| 20240101100000 |
| 20240105150000 |
| 20240110090000 |
| 20240115120000 |
+----------------+

Use Cases

  1. Check before deployment

    wheels dbmigrate info
  2. Verify after migration

    wheels dbmigrate latest
    wheels dbmigrate info
  3. Troubleshoot issues

    • See which migrations have run

    • Identify pending migrations

    • Confirm database version

Common Scenarios

All Migrations Complete

Current Version: 20240125160000
Status: 6 completed, 0 pending
✓ Database is up to date

Fresh Database

Current Version: 0
Status: 0 completed, 6 pending
⚠ No migrations have been run

Partial Migration

Current Version: 20240110090000
Status: 3 completed, 3 pending
⚠ Database needs migration

Troubleshooting

Migration Not Showing

  • Check file is in /db/migrate/

  • Verify .cfc extension

  • Ensure proper timestamp format

Version Mismatch

  • Check schema_migrations table

  • Verify migration files haven't been renamed

  • Look for duplicate timestamps

Connection Issues

  • Verify datasource configuration

  • Check database credentials

  • Ensure database server is running

Integration with CI/CD

Use in deployment scripts:

#!/bin/bash
# Check migration status
wheels dbmigrate info

# Run if needed
if [[ $(wheels dbmigrate info | grep "pending") ]]; then
    echo "Running pending migrations..."
    wheels dbmigrate latest
fi

Best Practices

  1. Always check info before running migrations

  2. Review pending migrations before deployment

  3. Keep migration files in version control

  4. Don't modify completed migration files

  5. Use info to verify production deployments

See Also

wheels scaffold

Generate complete CRUD scaffolding for a resource.

Synopsis

wheels scaffold [name] [options]

Description

The wheels scaffold command generates a complete CRUD (Create, Read, Update, Delete) implementation including model, controller, views, tests, and database migration. It's the fastest way to create a fully functional resource.

Arguments

Argument
Description
Default

name

Resource name (singular)

Required

Options

Option
Description
Default

--properties

Model properties (name:type)

--belongs-to

Belongs to associations

--has-many

Has many associations

--api

Generate API-only scaffold

false

--tests

Generate test files

true

--migrate

Run migration after generation

false

--force

Overwrite existing files

false

--help

Show help information

Examples

Basic scaffold

wheels scaffold product

Scaffold with properties

wheels scaffold product --properties="name:string,price:decimal,stock:integer"

Scaffold with associations

wheels scaffold order --properties="total:decimal,status:string" \
  --belongs-to="user" --has-many="orderItems"

API scaffold

wheels scaffold product --api --properties="name:string,price:decimal"

Scaffold with auto-migration

wheels scaffold category --properties="name:string" --migrate

What Gets Generated

Standard Scaffold

  1. Model (/models/Product.cfc)

    • Properties and validations

    • Associations

    • Business logic

  2. Controller (/controllers/Products.cfc)

    • All CRUD actions

    • Flash messages

    • Error handling

  3. Views (/views/products/)

    • index.cfm - List all records

    • show.cfm - Display single record

    • new.cfm - New record form

    • edit.cfm - Edit record form

    • _form.cfm - Shared form partial

  4. Migration (/db/migrate/[timestamp]_create_products.cfc)

    • Create table

    • Add indexes

    • Define columns

  5. Tests (if enabled)

    • Model tests

    • Controller tests

    • Integration tests

API Scaffold

  1. Model - Same as standard

  2. API Controller - JSON responses only

  3. Migration - Same as standard

  4. API Tests - JSON response tests

  5. No Views - API doesn't need views

Generated Files Example

For wheels scaffold product --properties="name:string,price:decimal,stock:integer":

Model: /models/Product.cfc

component extends="Model" {

    function init() {
        // Properties
        property(name="name", label="Product Name");
        property(name="price", label="Price");
        property(name="stock", label="Stock Quantity");
        
        // Validations
        validatesPresenceOf("name,price,stock");
        validatesUniquenessOf("name");
        validatesNumericalityOf("price", greaterThan=0);
        validatesNumericalityOf("stock", onlyInteger=true, greaterThanOrEqualTo=0);
    }

}

Controller: /controllers/Products.cfc

component extends="Controller" {

    function init() {
        // Filters
    }

    function index() {
        products = model("Product").findAll(order="name");
    }

    function show() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            flashInsert(error="Product not found.");
            redirectTo(action="index");
        }
    }

    function new() {
        product = model("Product").new();
    }

    function create() {
        product = model("Product").new(params.product);
        if (product.save()) {
            flashInsert(success="Product was created successfully.");
            redirectTo(action="index");
        } else {
            flashInsert(error="There was an error creating the product.");
            renderView(action="new");
        }
    }

    function edit() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            flashInsert(error="Product not found.");
            redirectTo(action="index");
        }
    }

    function update() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.update(params.product)) {
            flashInsert(success="Product was updated successfully.");
            redirectTo(action="index");
        } else {
            flashInsert(error="There was an error updating the product.");
            renderView(action="edit");
        }
    }

    function delete() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.delete()) {
            flashInsert(success="Product was deleted successfully.");
        } else {
            flashInsert(error="Product could not be deleted.");
        }
        redirectTo(action="index");
    }

}

View: /views/products/index.cfm

<h1>Products</h1>

#flashMessages()#

<p>#linkTo(text="New Product", action="new", class="btn btn-primary")#</p>

<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Price</th>
            <th>Stock</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        <cfloop query="products">
            <tr>
                <td>#encodeForHtml(products.name)#</td>
                <td>#dollarFormat(products.price)#</td>
                <td>#products.stock#</td>
                <td>
                    #linkTo(text="Show", action="show", key=products.id)#
                    #linkTo(text="Edit", action="edit", key=products.id)#
                    #linkTo(text="Delete", action="delete", key=products.id, 
                            method="delete", confirm="Are you sure?")#
                </td>
            </tr>
        </cfloop>
    </tbody>
</table>

Form Partial: /views/products/_form.cfm

#errorMessagesFor("product")#

#textField(objectName="product", property="name", label="Product Name")#
#textField(objectName="product", property="price", label="Price")#
#textField(objectName="product", property="stock", label="Stock Quantity")#

Migration: /db/migrate/[timestamp]_create_products.cfc

component extends="wheels.migrator.Migration" {

    function up() {
        transaction {
            t = createTable("products");
            t.string("name");
            t.decimal("price", precision=10, scale=2);
            t.integer("stock");
            t.timestamps();
            t.create();
            
            addIndex(table="products", columns="name", unique=true);
        }
    }

    function down() {
        transaction {
            dropTable("products");
        }
    }

}

Routes Configuration

Add to /config/routes.cfm:

<cfset resources("products")>

This creates all RESTful routes:

  • GET /products - index

  • GET /products/new - new

  • POST /products - create

  • GET /products/[key] - show

  • GET /products/[key]/edit - edit

  • PUT/PATCH /products/[key] - update

  • DELETE /products/[key] - delete

Post-Scaffold Steps

  1. Run migration (if not using --migrate):

    wheels dbmigrate latest
  2. Add routes to /config/routes.cfm:

    <cfset resources("products")>
  3. Restart application:

    wheels reload
  4. Test the scaffold:

    • Visit /products to see the index

    • Create, edit, and delete records

    • Run generated tests

Customization

Adding Search

In controller's index():

function index() {
    if (StructKeyExists(params, "search")) {
        products = model("Product").findAll(
            where="name LIKE :search",
            params={search: "%#params.search#%"}
        );
    } else {
        products = model("Product").findAll();
    }
}

Adding Pagination

function index() {
    products = model("Product").findAll(
        page=params.page ?: 1,
        perPage=20,
        order="createdAt DESC"
    );
}

Adding Filters

function init() {
    filters(through="authenticate", except="index,show");
}

Best Practices

  1. Properties: Define all needed properties upfront

  2. Associations: Include relationships in initial scaffold

  3. Validation: Add custom validations after generation

  4. Testing: Always generate and run tests

  5. Routes: Use RESTful resources when possible

  6. Security: Add authentication/authorization

Comparison with Individual Generators

Scaffold generates everything at once:

# Scaffold does all of this:
wheels generate model product --properties="name:string,price:decimal"
wheels generate controller products --rest
wheels generate view products index,show,new,edit,_form
wheels generate test model product
wheels generate test controller products
wheels dbmigrate create table products

See Also

wheels dbmigrate latest

Run all pending database migrations to bring database to latest version.

Synopsis

wheels dbmigrate latest

Description

The wheels dbmigrate latest command runs all pending migrations in chronological order, updating your database schema to the latest version. This is the most commonly used migration command.

Options

Option
Description

--help

Show help information

How It Works

  1. Checks current database version

  2. Identifies all pending migrations

  3. Runs each migration in order

  4. Updates version after each success

  5. Stops on first error

Example Output

╔═══════════════════════════════════════════════╗
║          Running Pending Migrations           ║
╚═══════════════════════════════════════════════╝

Current Version: 20240110090000
Target Version: 20240125160000

Migrating...

→ Running 20240115120000_create_orders_table.cfc
  Creating table: orders
  Adding indexes...
  ✓ Success (0.125s)

→ Running 20240120140000_add_status_to_orders.cfc
  Adding column: status to orders
  ✓ Success (0.089s)

→ Running 20240125160000_create_categories_table.cfc
  Creating table: categories
  Adding foreign keys...
  ✓ Success (0.143s)

╔═══════════════════════════════════════════════╗
║            Migration Complete                 ║
╚═══════════════════════════════════════════════╝

Previous Version: 20240110090000
Current Version:  20240125160000
Migrations Run:   3
Total Time:       0.357s

Migration Execution

Each migration file must contain:

component extends="wheels.migrator.Migration" {

    function up() {
        // Database changes go here
        transaction {
            // Use transaction for safety
        }
    }

    function down() {
        // Rollback logic (optional)
        transaction {
            // Reverse the up() changes
        }
    }

}

Transaction Safety

Migrations run within transactions:

  • All changes in a migration succeed or fail together

  • Database remains consistent

  • Failed migrations can be retried

Common Migration Operations

Create Table

function up() {
    transaction {
        t = createTable("products");
        t.string("name");
        t.decimal("price");
        t.timestamps();
        t.create();
    }
}

Add Column

function up() {
    transaction {
        addColumn(table="users", column="email", type="string");
    }
}

Add Index

function up() {
    transaction {
        addIndex(table="users", columns="email", unique=true);
    }
}

Modify Column

function up() {
    transaction {
        changeColumn(table="products", column="price", type="decimal", precision=10, scale=2);
    }
}

Error Handling

If a migration fails:

→ Running 20240120140000_add_status_to_orders.cfc
  Adding column: status to orders
  ✗ ERROR: Column 'status' already exists

Migration failed at version 20240115120000
Error: Column 'status' already exists in table 'orders'

To retry: Fix the migration file and run 'wheels dbmigrate latest' again
To skip: Run 'wheels dbmigrate up' to run one at a time

Best Practices

  1. Test migrations locally first

    # Test on development database
    wheels dbmigrate latest
    
    # Verify
    wheels dbmigrate info
  2. Backup before production migrations

    # Backup database
    mysqldump myapp_production > backup.sql
    
    # Run migrations
    wheels dbmigrate latest
  3. Use transactions

    function up() {
        transaction {
            // All changes here
        }
    }
  4. Make migrations reversible

    function down() {
        transaction {
            dropTable("products");
        }
    }

Environment-Specific Migrations

Migrations can check environment:

function up() {
    transaction {
        // Always run
        addColumn(table="users", column="lastLogin", type="datetime");
        
        // Development only
        if (get("environment") == "development") {
            // Add test data
            sql("INSERT INTO users (email) VALUES ('test@example.com')");
        }
    }
}

Dry Run

Preview migrations without running:

# Check what would run
wheels dbmigrate info

# Review migration files
ls db/migrate/

Performance Considerations

For large tables:

function up() {
    transaction {
        // Add index concurrently (if supported)
        if (get("databaseType") == "postgresql") {
            sql("CREATE INDEX CONCURRENTLY idx_users_email ON users(email)");
        } else {
            addIndex(table="users", columns="email");
        }
    }
}

Continuous Integration

Add to CI/CD pipeline:

# .github/workflows/deploy.yml
- name: Run migrations
  run: |
    wheels dbmigrate latest
    wheels test app

Rollback Strategy

If issues occur after migration:

  1. Use down migrations

    wheels dbmigrate down
    wheels dbmigrate down
  2. Restore from backup

    mysql myapp_production < backup.sql
  3. Fix and retry

    • Fix migration file

    • Run wheels dbmigrate latest

Common Issues

Timeout on Large Tables

function up() {
    // Increase timeout for large operations
    setting requestTimeout="300";
    
    transaction {
        // Long running operation
    }
}

Foreign Key Constraints

function up() {
    transaction {
        // Disable checks temporarily
        sql("SET FOREIGN_KEY_CHECKS=0");
        
        // Make changes
        dropTable("orders");
        
        // Re-enable
        sql("SET FOREIGN_KEY_CHECKS=1");
    }
}

See Also

wheels dbmigrate up

Run the next pending database migration.

Synopsis

wheels dbmigrate up [options]

Description

The dbmigrate up command executes the next pending migration in your database migration queue. This command is used to incrementally apply database changes one migration at a time, allowing for controlled and reversible database schema updates.

Options

--env

  • Type: String

  • Default: development

  • Description: The environment to run the migration in (development, testing, production)

--datasource

  • Type: String

  • Default: Application default

  • Description: Specify a custom datasource for the migration

--verbose

  • Type: Boolean

  • Default: false

  • Description: Display detailed output during migration execution

--dry-run

  • Type: Boolean

  • Default: false

  • Description: Preview the migration without executing it

Examples

Run the next pending migration

wheels dbmigrate up

Run migration in production environment

wheels dbmigrate up --env=production

Preview migration without executing

wheels dbmigrate up --dry-run --verbose

Use a specific datasource

wheels dbmigrate up --datasource=myCustomDB

Use Cases

Incremental Database Updates

When you want to apply database changes one at a time rather than all at once:

# Check pending migrations
wheels dbmigrate info

# Apply next migration
wheels dbmigrate up

# Verify the change
wheels dbmigrate info

Testing Individual Migrations

Test migrations individually before applying all pending changes:

# Run in test environment first
wheels dbmigrate up --env=testing

# If successful, apply to development
wheels dbmigrate up --env=development

Controlled Production Deployments

Apply migrations incrementally in production for better control:

# Preview the migration
wheels dbmigrate up --dry-run --env=production

# Apply if preview looks good
wheels dbmigrate up --env=production

# Monitor application
# If issues arise, can rollback with 'wheels dbmigrate down'

Notes

  • Migrations are executed in chronological order based on their timestamps

  • Each migration is tracked in the database to prevent duplicate execution

  • Use wheels dbmigrate info to see pending and completed migrations

  • Always backup your database before running migrations in production

Related Commands

wheels dbmigrate down

Rollback the last executed database migration.

Synopsis

wheels dbmigrate down [options]

Description

The dbmigrate down command reverses the last executed migration by running its down() method. This is useful for undoing database changes when issues are discovered or when you need to modify a migration. The command ensures safe rollback of schema changes while maintaining database integrity.

Options

--env

  • Type: String

  • Default: development

  • Description: The environment to rollback the migration in

--datasource

  • Type: String

  • Default: Application default

  • Description: Specify a custom datasource for the rollback

--verbose

  • Type: Boolean

  • Default: false

  • Description: Display detailed output during rollback

--force

  • Type: Boolean

  • Default: false

  • Description: Force rollback even if there are warnings

--dry-run

  • Type: Boolean

  • Default: false

  • Description: Preview the rollback without executing it

Examples

Rollback the last migration

wheels dbmigrate down

Rollback in production with confirmation

wheels dbmigrate down --env=production --verbose

Preview rollback without executing

wheels dbmigrate down --dry-run

Force rollback with custom datasource

wheels dbmigrate down --datasource=legacyDB --force

Use Cases

Fixing Migration Errors

When a migration contains errors or needs modification:

# Run the migration
wheels dbmigrate up

# Discover an issue
# Rollback the migration
wheels dbmigrate down

# Edit the migration file
# Re-run the migration
wheels dbmigrate up

Development Iteration

During development when refining migrations:

# Apply migration
wheels dbmigrate up

# Test the changes
# Need to modify? Rollback
wheels dbmigrate down

# Make changes to migration
# Apply again
wheels dbmigrate up

Emergency Production Rollback

When a production migration causes issues:

# Check current migration status
wheels dbmigrate info --env=production

# Rollback the problematic migration
wheels dbmigrate down --env=production --verbose

# Verify rollback
wheels dbmigrate info --env=production

Important Considerations

Data Loss Warning

Rolling back migrations that drop columns or tables will result in data loss. Always ensure you have backups before rolling back destructive migrations.

Down Method Requirements

For a migration to be rolled back, it must have a properly implemented down() method that reverses the changes made in the up() method.

Migration Dependencies

Be cautious when rolling back migrations that other migrations depend on. This can break the migration chain.

Best Practices

  1. Always implement down() methods: Even if you think you'll never need to rollback

  2. Test rollbacks: In development, always test that your down() method works correctly

  3. Backup before rollback: Especially in production environments

  4. Document destructive operations: Clearly indicate when rollbacks will cause data loss

Notes

  • Only the last executed migration can be rolled back with this command

  • To rollback multiple migrations, run the command multiple times

  • The migration version is removed from the database tracking table upon successful rollback

  • Some operations (like dropping columns with data) cannot be fully reversed

Related Commands

wheels dbmigrate create blank

Create an empty database migration file with up and down methods.

Synopsis

Description

The dbmigrate create blank command generates a new empty migration file with the basic structure including up() and down() methods. This provides a starting point for custom migrations where you need full control over the migration logic.

Options

--name

  • Type: String

  • Required: Yes

  • Description: The name of the migration (will be prefixed with timestamp)

--datasource

  • Type: String

  • Default: Application default

  • Description: Specify the datasource this migration targets

--description

  • Type: String

  • Default: Empty

  • Description: Add a description comment to the migration file

--template

  • Type: String

  • Default: blank

  • Description: Use a custom template for the migration

Examples

Create a basic empty migration

Create migration with description

Create migration for specific datasource

Generated File Structure

The command creates a file named YYYYMMDDHHmmss_<name>.cfc with the following structure:

Use Cases

Custom Database Operations

For complex operations not covered by other generators:

Data Migrations

When you need to migrate data, not just schema:

Multi-Step Operations

For migrations requiring multiple coordinated changes:

Database-Specific Features

For database-specific features not abstracted by CFWheels:

Best Practices

1. Descriptive Names

Use clear, descriptive names that indicate the migration's purpose:

2. Implement Both Methods

Always implement both up() and down() methods:

3. Use Transactions

Wrap operations in transactions for atomicity:

4. Add Comments

Document complex operations:

Available Migration Methods

Within your blank migration, you can use these helper methods:

  • createTable(name, options) - Create a new table

  • dropTable(name) - Drop a table

  • addColumn(table, column, type, options) - Add a column

  • removeColumn(table, column) - Remove a column

  • changeColumn(table, column, type, options) - Modify a column

  • addIndex(table, column, options) - Add an index

  • removeIndex(table, column) - Remove an index

  • execute(sql) - Execute raw SQL

  • announce(message) - Output a message during migration

Notes

  • Migration files are created in /db/migrate/ or your configured migration path

  • The timestamp ensures migrations run in the correct order

  • Always test migrations in development before production

  • Keep migrations focused on a single purpose

Related Commands

wheels dbmigrate create table

Generate a migration file for creating a new database table.

Synopsis

Description

The dbmigrate create table command generates a migration file that creates a new database table with specified columns. It automatically includes timestamp columns (createdAt, updatedAt) and provides a complete table structure following CFWheels conventions.

Arguments

<table_name>

  • Type: String

  • Required: Yes

  • Description: The name of the table to create (singular form recommended)

[columns...]

  • Type: String (multiple)

  • Required: No

  • Format: name:type:options

  • Description: Column definitions in the format name:type:options

Options

--id

  • Type: String

  • Default: id

  • Description: Name of the primary key column (use --no-id to skip)

--no-id

  • Type: Boolean

  • Default: false

  • Description: Skip creating a primary key column

--timestamps

  • Type: Boolean

  • Default: true

  • Description: Include createdAt and updatedAt columns

--no-timestamps

  • Type: Boolean

  • Default: false

  • Description: Skip creating timestamp columns

--datasource

  • Type: String

  • Default: Application default

  • Description: Target datasource for the migration

--force

  • Type: Boolean

  • Default: false

  • Description: Overwrite existing migration file

Column Types

Supported column types:

  • string - VARCHAR(255)

  • text - TEXT/CLOB

  • integer - INTEGER

  • biginteger - BIGINT

  • float - FLOAT

  • decimal - DECIMAL

  • boolean - BOOLEAN/BIT

  • date - DATE

  • time - TIME

  • datetime - DATETIME/TIMESTAMP

  • timestamp - TIMESTAMP

  • binary - BLOB/BINARY

Column Options

Column options are specified after the type with colons:

  • :null - Allow NULL values

  • :default=value - Set default value

  • :limit=n - Set column length/size

  • :precision=n - Set decimal precision

  • :scale=n - Set decimal scale

Examples

Create a basic table

Create table with columns

Create table with column options

Create table without timestamps

Create join table without primary key

Generated Migration Example

For the command:

Generates:

Use Cases

Standard Entity Table

Create a typical entity table:

Join Table for Many-to-Many

Create a join table for relationships:

Configuration Table

Create a settings/configuration table:

Audit Log Table

Create an audit trail table:

Best Practices

1. Use Singular Table Names

CFWheels conventions expect singular table names:

2. Include Foreign Keys

Add foreign key columns for relationships:

3. Set Appropriate Defaults

Provide sensible defaults where applicable:

4. Consider Indexes

Plan for indexes (add them in separate migrations):

Advanced Options

Custom Primary Key

Specify a custom primary key name:

Composite Keys

For composite primary keys, use blank migration:

Notes

  • Table names should follow your database naming conventions

  • The migration automatically handles rollback with dropTable()

  • Column order in the command is preserved in the migration

  • Use wheels dbmigrate up to run the generated migration

Related Commands

wheels dbmigrate exec

Execute a specific database migration by version number.

Synopsis

Description

The dbmigrate exec command allows you to run a specific migration identified by its version number, regardless of the current migration state. This is useful for applying individual migrations out of sequence during development or for special maintenance operations.

Arguments

<version>

  • Type: String

  • Required: Yes

  • Description: The version number of the migration to execute (e.g., 20240115123456)

Options

--env

  • Type: String

  • Default: development

  • Description: The environment to run the migration in

--datasource

  • Type: String

  • Default: Application default

  • Description: Specify a custom datasource for the migration

--direction

  • Type: String

  • Default: up

  • Values: up, down

  • Description: Direction to run the migration (up or down)

--force

  • Type: Boolean

  • Default: false

  • Description: Force execution even if migration is already run

--verbose

  • Type: Boolean

  • Default: false

  • Description: Display detailed output during execution

--dry-run

  • Type: Boolean

  • Default: false

  • Description: Preview the migration without executing it

Examples

Execute a specific migration

Execute migration's down method

Force re-execution of a migration

Execute in production environment

Preview migration execution

Use Cases

Applying Hotfix Migrations

Apply a critical fix out of sequence:

Re-running Failed Migrations

When a migration partially fails:

Testing Specific Migrations

Test individual migrations during development:

Selective Migration Application

Apply only certain migrations from a set:

Important Considerations

Migration Order

Executing migrations out of order can cause issues if migrations have dependencies. Always ensure that any required preceding migrations have been run.

Version Tracking

The command updates the migration tracking table to reflect the execution status. Using --force will update the timestamp of execution.

Down Direction

When running with --direction=down, the migration must have already been executed (unless --force is used).

Best Practices

  1. Check Dependencies: Ensure required migrations are already applied

  2. Test First: Run in development/testing before production

  3. Use Sparingly: Prefer normal migration flow with up/latest

  4. Document Usage: Record when and why specific executions were done

  5. Verify State: Check migration status before and after execution

Version Number Format

Migration versions are typically timestamps in the format:

  • YYYYMMDDHHmmss (e.g., 20240115123456)

  • Year: 2024

  • Month: 01

  • Day: 15

  • Hour: 12

  • Minute: 34

  • Second: 56

Notes

  • The migration file must exist in the migrations directory

  • Using --force bypasses normal safety checks

  • The command will fail if the migration file has syntax errors

  • Both up() and down() methods should be defined in the migration

Related Commands

wheels dbmigrate reset

Reset all database migrations by rolling back all executed migrations and optionally re-running them.

Synopsis

Description

The dbmigrate reset command provides a way to completely reset your database migrations. It rolls back all executed migrations in reverse order and can optionally re-run them all. This is particularly useful during development when you need to start fresh or when testing migration sequences.

Options

--env

  • Type: String

  • Default: development

  • Description: The environment to reset migrations in

--datasource

  • Type: String

  • Default: Application default

  • Description: Specify a custom datasource for the reset

--remigrate

  • Type: Boolean

  • Default: false

  • Description: After rolling back all migrations, run them all again

--verbose

  • Type: Boolean

  • Default: false

  • Description: Display detailed output during reset

--force

  • Type: Boolean

  • Default: false

  • Description: Skip confirmation prompts (use with caution)

--dry-run

  • Type: Boolean

  • Default: false

  • Description: Preview the reset without executing it

Examples

Reset all migrations (rollback only)

Reset and re-run all migrations

Reset in testing environment with verbose output

Force reset without confirmation

Preview reset operation

Use Cases

Fresh Development Database

Start with a clean slate during development:

Testing Migration Sequence

Verify that all migrations run correctly from scratch:

Fixing Migration Order Issues

When migrations have dependency problems:

Continuous Integration Setup

Reset database for each test run:

Important Warnings

Data Loss

WARNING: This command will result in complete data loss as it rolls back all migrations. Always ensure you have proper backups before running this command, especially in production environments.

Production Usage

Using this command in production is strongly discouraged. If you must use it in production:

  1. Take a complete database backup

  2. Put the application in maintenance mode

  3. Use the confirmation prompts (don't use --force)

  4. Have a rollback plan ready

Migration Dependencies

The reset process rolls back migrations in reverse chronological order. Ensure all your down() methods are properly implemented.

Best Practices

  1. Development Only: Primarily use this command in development environments

  2. Backup First: Always backup your database before resetting

  3. Test Down Methods: Ensure all migrations have working down() methods

  4. Use Confirmation: Don't use --force unless in automated environments

  5. Document Usage: If used in production, document when and why

Process Flow

  1. Confirms the operation (unless --force is used)

  2. Retrieves all executed migrations

  3. Rolls back each migration in reverse order

  4. Clears the migration tracking table

  5. If --remigrate is specified, runs all migrations again

Notes

  • The command will fail if any migration's down() method fails

  • Migration files must still exist for rollback to work

  • The migration tracking table itself is preserved

  • Use wheels dbmigrate info after reset to verify status

Related Commands

wheels dbmigrate remove table

Generate a migration file for dropping a database table.

Synopsis

Description

The dbmigrate remove table command generates a migration file that drops an existing database table. The generated migration includes both the drop operation and a reversible up method that recreates the table structure, making the migration fully reversible.

Arguments

<table_name>

  • Type: String

  • Required: Yes

  • Description: The name of the table to drop

Options

--datasource

  • Type: String

  • Default: Application default

  • Description: Target datasource for the migration

--force

  • Type: Boolean

  • Default: false

  • Description: Skip safety prompts and generate migration immediately

--no-backup

  • Type: Boolean

  • Default: false

  • Description: Don't include table structure backup in the down() method

--cascade

  • Type: Boolean

  • Default: false

  • Description: Include CASCADE option to drop dependent objects

Examples

Basic table removal

Remove table with cascade

Force removal without prompts

Remove without backup structure

Generated Migration Example

For the command:

Generates:

Use Cases

Removing Temporary Tables

Clean up temporary or staging tables:

Refactoring Database Schema

Remove tables during schema refactoring:

Cleaning Up Failed Features

Remove tables from cancelled features:

Archive Table Cleanup

Remove old archive tables:

Safety Considerations

Data Loss Warning

CRITICAL: Dropping a table permanently deletes all data. Always:

  1. Backup the table data before removal

  2. Verify data has been migrated if needed

  3. Test in development/staging first

  4. Have a rollback plan

Dependent Objects

Consider objects that depend on the table:

  • Foreign key constraints

  • Views

  • Stored procedures

  • Triggers

  • Application code

Using CASCADE

The --cascade option drops dependent objects:

Best Practices

1. Document Removals

Add clear documentation about why the table is being removed:

2. Backup Data First

Before removing tables, create data backups:

3. Staged Removal

For production systems, consider staged removal:

4. Check Dependencies

Verify no active dependencies before removal:

Migration Structure Details

With Backup (Default)

The generated down() method includes table structure:

Without Backup

With --no-backup, down() is simpler:

Recovery Strategies

If Removal Was Mistake

  1. Don't run the migration in production

  2. Use wheels dbmigrate down if already run

  3. Restore from backup if down() fails

Preserving Table Structure

Before removal, capture structure:

Notes

  • The command analyzes table structure before generating migration

  • Foreign key constraints must be removed before table removal

  • The migration is reversible if table structure is preserved

  • Always review generated migration before running

Related Commands

wheels dbmigrate create column

Generate a migration file for adding columns to an existing database table.

Synopsis

Description

The dbmigrate create column command generates a migration file that adds one or more columns to an existing database table. It supports all standard column types and options, making it easy to evolve your database schema incrementally.

Arguments

<table_name>

  • Type: String

  • Required: Yes

  • Description: The name of the table to add columns to

<column_name>:<type>[:options]

  • Type: String

  • Required: Yes (at least one)

  • Format: name:type:option1:option2=value

  • Description: Column definition(s) to add

Options

--datasource

  • Type: String

  • Default: Application default

  • Description: Target datasource for the migration

--after

  • Type: String

  • Default: None

  • Description: Position new column(s) after specified column

--force

  • Type: Boolean

  • Default: false

  • Description: Overwrite existing migration file

Column Types

  • string - VARCHAR(255)

  • text - TEXT/CLOB

  • integer - INTEGER

  • biginteger - BIGINT

  • float - FLOAT

  • decimal - DECIMAL

  • boolean - BOOLEAN/BIT

  • date - DATE

  • time - TIME

  • datetime - DATETIME/TIMESTAMP

  • timestamp - TIMESTAMP

  • binary - BLOB/BINARY

Column Options

  • :null - Allow NULL values

  • :default=value - Set default value

  • :limit=n - Set column length

  • :precision=n - Set decimal precision

  • :scale=n - Set decimal scale

  • :index - Create an index on this column

  • :unique - Add unique constraint

Examples

Add a single column

Add multiple columns

Add column with positioning

Add columns with indexes

Generated Migration Example

For the command:

Generates:

Use Cases

Adding User Preferences

Add preference columns to user table:

Adding Audit Fields

Add tracking columns to any table:

Adding Calculated Fields

Add columns for denormalized/cached data:

Adding Search Columns

Add columns optimized for searching:

Best Practices

1. Consider NULL Values

For existing tables with data, make new columns nullable or provide defaults:

2. Use Appropriate Types

Choose the right column type for your data:

3. Plan for Indexes

Add indexes for columns used in queries:

4. Group Related Changes

Add related columns in a single migration:

Advanced Scenarios

Adding Foreign Keys

Add foreign key columns with appropriate types:

Adding JSON Columns

For databases that support JSON:

Positional Columns

Control column order in table:

Common Pitfalls

1. Non-Nullable Without Default

2. Changing Column Types

This command adds columns, not modifies them:

Notes

  • The migration includes automatic rollback with removeColumn()

  • Column order in down() is reversed for proper rollback

  • Always test migrations with data in development

  • Consider the impact on existing queries and code

Related Commands

- Create migrations

- Add properties to existing models

- Generate controllers

- Generate complete CRUD

- Generate complete CRUD with routes

- Generate controllers

- Generate RESTful resources

- Generate API resources

- Generate models

- Create columns

- Generate tests

- Generate full resources

- Generate controllers

- Generate models

- Generate routes

- Generate view files

- Generate controllers

- Generate complete CRUD

#wheels channel

- Interactive CRUD generation

- Generate models only

- Generate controllers only

- Generate API resources

- Generate routes only

- Run tests

- Test coverage

- Testing documentation

- Generate controllers

- Generate models

- Generate complete resources

- Run all pending migrations

- Run next migration

- Rollback migration

- Create new migration

- Generate models

- Generate controllers

- Generate REST resources

- Run migrations

- Check migration status

- Run single migration

- Rollback migration

- Create migration

- Rollback the last migration

- Run all pending migrations

- View migration status

- Reset all migrations

- Run the next migration

- Reset all migrations

- View migration status

- Run a specific migration

- Create a table migration

- Create a column migration

- Run migrations

- Rollback migrations

- View migration status

- Add columns to existing table

- Create custom migration

- Create table removal migration

- Run migrations

- View migration status

- Run the next migration

- Rollback last migration

- Run all pending migrations

- View migration status

- Create a new migration

- Run the next migration

- Rollback last migration

- Run all pending migrations

- View migration status

- Seed the database with data

- Create tables

- Create custom migrations

- Run migrations

- Rollback migrations

- Export table schemas

- Create new tables

- Create custom migrations

- Remove tables

- Run migrations

- Rollback migrations

wheels dbmigrate create table
wheels generate property
wheels generate controller
wheels scaffold
wheels scaffold
wheels generate controller
wheels generate resource
wheels generate api-resource
wheels generate model
wheels dbmigrate create column
wheels generate test
wheels generate resource
wheels generate controller
wheels generate model
wheels generate route
wheels generate view
wheels generate controller
wheels scaffold
Service Architecture
Testing Guide
Migration Guide
Wheels Documentation
GitHub Discussions
CFML Slack
wheels scaffold
wheels generate model
wheels generate controller
wheels generate api-resource
wheels generate route
wheels test run
wheels test coverage
Testing Guide
wheels generate controller
wheels generate model
wheels scaffold
wheels dbmigrate latest
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate create blank
wheels generate model
wheels generate controller
wheels generate resource
wheels dbmigrate latest
wheels dbmigrate info
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate create blank
wheels dbmigrate down
wheels dbmigrate latest
wheels dbmigrate info
wheels dbmigrate reset
wheels dbmigrate up
wheels dbmigrate reset
wheels dbmigrate info
wheels dbmigrate exec
wheels dbmigrate create blank --name=<name> [options]
wheels dbmigrate create blank --name=add_custom_indexes
wheels dbmigrate create blank --name=update_user_permissions --description="Add role-based permissions to users"
wheels dbmigrate create blank --name=legacy_data_cleanup --datasource=legacyDB
component extends="wheels.migrator.Migration" hint="<description>" {

    function up() {
        transaction {
            // Add your migration code here
        }
    }

    function down() {
        transaction {
            // Add code to reverse the migration
        }
    }

}
# Create migration for custom stored procedure
wheels dbmigrate create blank --name=create_reporting_procedures

# Edit the file to add:
# - CREATE PROCEDURE statements
# - Complex SQL operations
# - Multiple related changes
# Create data migration
wheels dbmigrate create blank --name=normalize_user_emails

# Edit to add data transformation logic
# Example: lowercase all email addresses
# Create complex migration
wheels dbmigrate create blank --name=refactor_order_system

# Edit to include:
# - Create new tables
# - Migrate data
# - Drop old tables
# - Update foreign keys
# Create migration for PostgreSQL-specific features
wheels dbmigrate create blank --name=add_json_columns

# Edit to use PostgreSQL JSON operations
# Good
wheels dbmigrate create blank --name=add_user_authentication_tokens

# Bad
wheels dbmigrate create blank --name=update1
function up() {
    transaction {
        execute("CREATE INDEX idx_users_email ON users(email)");
    }
}

function down() {
    transaction {
        execute("DROP INDEX idx_users_email");
    }
}
function up() {
    transaction {
        // All operations succeed or all fail
        createTable("new_table");
        execute("INSERT INTO new_table SELECT * FROM old_table");
        dropTable("old_table");
    }
}
function up() {
    transaction {
        // Create composite index for query optimization
        // This supports the findActiveUsersByRegion() query
        execute("
            CREATE INDEX idx_users_active_region 
            ON users(is_active, region_id) 
            WHERE is_active = 1
        ");
    }
}
wheels dbmigrate create table <table_name> [columns...] [options]
wheels dbmigrate create table user
wheels dbmigrate create table post title:string content:text author_id:integer published:boolean
wheels dbmigrate create table product name:string:limit=100 price:decimal:precision=10:scale=2 description:text:null
wheels dbmigrate create table configuration key:string value:text --no-timestamps
wheels dbmigrate create table users_roles user_id:integer role_id:integer --no-id
wheels dbmigrate create table post title:string content:text author_id:integer published:boolean
component extends="wheels.migrator.Migration" hint="Create post table" {

    function up() {
        transaction {
            createTable(name="post", force=false) {
                t.increments("id");
                t.string("title");
                t.text("content");
                t.integer("author_id");
                t.boolean("published");
                t.timestamps();
            }
        }
    }

    function down() {
        transaction {
            dropTable("post");
        }
    }

}
wheels dbmigrate create table customer \
  first_name:string \
  last_name:string \
  email:string:limit=150 \
  phone:string:null \
  is_active:boolean:default=true
wheels dbmigrate create table products_categories \
  product_id:integer \
  category_id:integer \
  display_order:integer:default=0 \
  --no-id
wheels dbmigrate create table setting \
  key:string:limit=50 \
  value:text \
  description:text:null \
  --no-timestamps
wheels dbmigrate create table audit_log \
  table_name:string \
  record_id:integer \
  action:string:limit=10 \
  user_id:integer \
  old_values:text:null \
  new_values:text:null \
  ip_address:string:limit=45
# Good
wheels dbmigrate create table user
wheels dbmigrate create table product

# Avoid
wheels dbmigrate create table users
wheels dbmigrate create table products
wheels dbmigrate create table order \
  customer_id:integer \
  total:decimal:precision=10:scale=2 \
  status:string:default='pending'
wheels dbmigrate create table article \
  title:string \
  content:text \
  is_published:boolean:default=false \
  view_count:integer:default=0
# Create table
wheels dbmigrate create table user email:string username:string

# Create index migration
wheels dbmigrate create blank --name=add_user_indexes
wheels dbmigrate create table legacy_customer \
  customer_code:string \
  name:string \
  --id=customer_code
# First create without primary key
wheels dbmigrate create table order_item \
  order_id:integer \
  product_id:integer \
  quantity:integer \
  --no-id

# Then create blank migration for composite key
wheels dbmigrate create blank --name=add_order_item_composite_key
wheels dbmigrate exec <version> [options]
wheels dbmigrate exec 20240115123456
wheels dbmigrate exec 20240115123456 --direction=down
wheels dbmigrate exec 20240115123456 --force --verbose
wheels dbmigrate exec 20240115123456 --env=production
wheels dbmigrate exec 20240115123456 --dry-run --verbose
# Create hotfix migration
wheels dbmigrate create blank --name=hotfix_critical_issue

# Execute it immediately
wheels dbmigrate exec 20240115123456 --env=production
# Check migration status
wheels dbmigrate info

# Re-run the failed migration
wheels dbmigrate exec 20240115123456 --force --verbose
# Run specific migration up
wheels dbmigrate exec 20240115123456

# Test the changes
# Run it down
wheels dbmigrate exec 20240115123456 --direction=down
# List all migrations
wheels dbmigrate info

# Execute only the ones you need
wheels dbmigrate exec 20240115123456
wheels dbmigrate exec 20240115134567
wheels dbmigrate reset [options]
wheels dbmigrate reset
wheels dbmigrate reset --remigrate
wheels dbmigrate reset --env=testing --verbose
wheels dbmigrate reset --force --remigrate
wheels dbmigrate reset --dry-run --verbose
# Reset and rebuild database schema
wheels dbmigrate reset --remigrate --verbose

# Seed with test data
wheels db seed
# Reset all migrations
wheels dbmigrate reset

# Run migrations one by one to test
wheels dbmigrate up
wheels dbmigrate up
# ... continue as needed
# Reset all migrations
wheels dbmigrate reset

# Manually fix migration files
# Re-run all migrations
wheels dbmigrate latest
# CI script
wheels dbmigrate reset --env=testing --force --remigrate
wheels test run --env=testing
wheels dbmigrate remove table <table_name> [options]
wheels dbmigrate remove table temp_import_data
wheels dbmigrate remove table user --cascade
wheels dbmigrate remove table obsolete_log --force
wheels dbmigrate remove table temporary_data --no-backup
wheels dbmigrate remove table product_archive
component extends="wheels.migrator.Migration" hint="Drop product_archive table" {

    function up() {
        transaction {
            dropTable("product_archive");
        }
    }

    function down() {
        transaction {
            // Recreate table structure for rollback
            createTable(name="product_archive") {
                t.increments("id");
                t.string("name");
                t.text("description");
                t.decimal("price", precision=10, scale=2);
                t.timestamps();
            }
        }
    }

}
# Remove import staging table
wheels dbmigrate remove table temp_customer_import

# Remove data migration table
wheels dbmigrate remove table migration_backup_20240115
# Remove old table after data migration
wheels dbmigrate remove table legacy_orders --force

# Remove normalized table
wheels dbmigrate remove table user_preferences_old
# Remove tables from abandoned feature
wheels dbmigrate remove table beta_feature_data
wheels dbmigrate remove table beta_feature_settings
# Remove yearly archive tables
wheels dbmigrate remove table orders_archive_2020
wheels dbmigrate remove table orders_archive_2021
# Drops table and all dependent objects
wheels dbmigrate remove table user --cascade
# Create descriptive migration
wheels dbmigrate remove table obsolete_analytics_cache

# Then edit the migration to add comments
component extends="wheels.migrator.Migration" 
  hint="Remove obsolete_analytics_cache table - replaced by Redis caching" {
# First create backup migration
wheels dbmigrate create blank --name=backup_user_preferences_data

# Then remove table
wheels dbmigrate remove table user_preferences
# Stage 1: Rename table (keep for rollback)
wheels dbmigrate create blank --name=rename_orders_to_orders_deprecated

# Stage 2: After verification period, remove
wheels dbmigrate remove table orders_deprecated
-- Check foreign keys
SELECT * FROM information_schema.referential_constraints 
WHERE referenced_table_name = 'table_name';

-- Check views
SELECT * FROM information_schema.views 
WHERE table_schema = DATABASE() 
AND view_definition LIKE '%table_name%';
function down() {
    transaction {
        createTable(name="product") {
            t.increments("id");
            // All columns recreated
            t.timestamps();
        }
        // Indexes recreated
        addIndex(table="product", column="sku", unique=true);
    }
}
function down() {
    transaction {
        announce("Table structure not backed up - manual recreation required");
    }
}
# Export table structure
wheels db schema --table=user_preferences > user_preferences_backup.sql

# Then remove
wheels dbmigrate remove table user_preferences
wheels dbmigrate create column <table_name> <column_name>:<type>[:options] [more_columns...] [options]
wheels dbmigrate create column user email:string
wheels dbmigrate create column product sku:string:unique weight:decimal:precision=8:scale=2 is_featured:boolean:default=false
wheels dbmigrate create column user middle_name:string:null --after=first_name
wheels dbmigrate create column order shipped_at:datetime:index tracking_number:string:index
wheels dbmigrate create column user phone:string:null country_code:string:limit=2:default='US'
component extends="wheels.migrator.Migration" hint="Add columns to user table" {

    function up() {
        transaction {
            addColumn(table="user", column="phone", type="string", null=true);
            addColumn(table="user", column="country_code", type="string", limit=2, default="US");
        }
    }

    function down() {
        transaction {
            removeColumn(table="user", column="country_code");
            removeColumn(table="user", column="phone");
        }
    }

}
wheels dbmigrate create column user \
  newsletter_subscribed:boolean:default=true \
  notification_email:boolean:default=true \
  theme_preference:string:default='light'
wheels dbmigrate create column product \
  last_modified_by:integer:null \
  last_modified_at:datetime:null \
  version:integer:default=1
wheels dbmigrate create column order \
  item_count:integer:default=0 \
  subtotal:decimal:precision=10:scale=2:default=0 \
  tax_amount:decimal:precision=10:scale=2:default=0
wheels dbmigrate create column article \
  search_text:text:null \
  slug:string:unique:index \
  tags:string:null
# Good - nullable
wheels dbmigrate create column user bio:text:null

# Good - with default
wheels dbmigrate create column user status:string:default='active'

# Bad - will fail if table has data
wheels dbmigrate create column user required_field:string
# For short text
wheels dbmigrate create column user username:string:limit=50

# For long text
wheels dbmigrate create column post content:text

# For money
wheels dbmigrate create column invoice amount:decimal:precision=10:scale=2
# Add indexed column
wheels dbmigrate create column order customer_email:string:index

# Or create separate index migration
wheels dbmigrate create blank --name=add_order_customer_email_index
# Add all address fields together
wheels dbmigrate create column customer \
  address_line1:string \
  address_line2:string:null \
  city:string \
  state:string:limit=2 \
  postal_code:string:limit=10 \
  country:string:limit=2:default='US'
# Add foreign key column
wheels dbmigrate create column order customer_id:integer:index

# Then create constraint in blank migration
wheels dbmigrate create blank --name=add_order_customer_foreign_key
# Create blank migration for JSON column
wheels dbmigrate create blank --name=add_user_preferences_json
# Then manually add JSON column type
# Add after specific column
wheels dbmigrate create column user display_name:string --after=username
# This will fail if table has data
wheels dbmigrate create column user required_field:string

# Do this instead
wheels dbmigrate create column user required_field:string:default='TBD'
# Wrong - trying to change type
wheels dbmigrate create column user age:integer

# Right - use blank migration for modifications
wheels dbmigrate create blank --name=change_user_age_to_integer
wheels dbmigrate create table
wheels dbmigrate create column
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate info
wheels dbmigrate create column
wheels dbmigrate create blank
wheels dbmigrate remove table
wheels dbmigrate up
wheels dbmigrate info
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate latest
wheels dbmigrate info
wheels dbmigrate create blank
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate latest
wheels dbmigrate info
wheels db seed
wheels dbmigrate create table
wheels dbmigrate create blank
wheels dbmigrate up
wheels dbmigrate down
wheels db schema
wheels dbmigrate create table
wheels dbmigrate create blank
wheels dbmigrate remove table
wheels dbmigrate up
wheels dbmigrate down

wheels test coverage

Generate code coverage reports for your test suite.

Synopsis

wheels test coverage [options]

Description

The wheels test coverage command runs your test suite while collecting code coverage metrics. It generates detailed reports showing which parts of your code are tested and identifies areas that need more test coverage.

Options

Option
Description
Default

--bundles

Specific test bundles to include

All bundles

--specs

Specific test specs to run

All specs

--format

Report format (html, json, xml, console)

html

--output

Output directory for reports

./coverage

--threshold

Minimum coverage percentage required

0

--include

Paths to include in coverage

app/**

--exclude

Paths to exclude from coverage

tests/**,vendor/**

--fail-on-low

Fail if coverage below threshold

false

--open

Open HTML report after generation

true

--verbose

Show detailed output

false

--help

Show help information

Examples

Generate basic coverage report

wheels test coverage

Set coverage threshold

wheels test coverage --threshold=80 --fail-on-low

Generate multiple formats

wheels test coverage --format=html,json,xml

Coverage for specific bundles

wheels test coverage --bundles=models,controllers

Custom output directory

wheels test coverage --output=./reports/coverage

Exclude specific paths

wheels test coverage --exclude="app/legacy/**,app/temp/**"

What It Does

  1. Instruments Code: Adds coverage tracking to your application

  2. Runs Tests: Executes all specified tests

  3. Collects Metrics: Tracks which lines are executed

  4. Generates Reports: Creates coverage reports in requested formats

  5. Analyzes Results: Provides insights and recommendations

Coverage Metrics

Line Coverage

Percentage of code lines executed:

File: /app/models/User.cfc
Lines: 156/200 (78%)

Function Coverage

Percentage of functions tested:

Functions: 45/50 (90%)

Branch Coverage

Percentage of code branches tested:

Branches: 120/150 (80%)

Statement Coverage

Percentage of statements executed:

Statements: 890/1000 (89%)

Report Formats

HTML Report

Interactive web-based report:

wheels test coverage --format=html

Features:

  • File browser

  • Source code viewer

  • Line-by-line coverage

  • Sortable metrics

  • Trend charts

JSON Report

Machine-readable format:

wheels test coverage --format=json
{
  "summary": {
    "lines": { "total": 1000, "covered": 850, "percent": 85 },
    "functions": { "total": 100, "covered": 92, "percent": 92 },
    "branches": { "total": 200, "covered": 160, "percent": 80 }
  },
  "files": {
    "/app/models/User.cfc": {
      "lines": { "total": 200, "covered": 156, "percent": 78 }
    }
  }
}

XML Report

For CI/CD integration:

wheels test coverage --format=xml

Compatible with:

  • Jenkins

  • GitLab CI

  • GitHub Actions

  • SonarQube

Console Report

Quick terminal output:

wheels test coverage --format=console
Code Coverage Report
===================

Overall Coverage: 85.3%

File                          Lines    Funcs    Branch   Stmt
---------------------------- -------- -------- -------- --------
/app/models/User.cfc           78.0%    85.0%    72.0%    80.0%
/app/models/Order.cfc          92.0%    95.0%    88.0%    90.0%
/app/controllers/Users.cfc     85.0%    90.0%    82.0%    86.0%

Uncovered Files:
- /app/models/Legacy.cfc (0%)
- /app/helpers/Deprecated.cfc (0%)

Coverage Thresholds

Global Threshold

wheels test coverage --threshold=80

Per-Metric Thresholds

Configure in .wheels-coverage.json:

{
  "thresholds": {
    "global": 80,
    "lines": 85,
    "functions": 90,
    "branches": 75,
    "statements": 85
  }
}

File-Specific Thresholds

{
  "thresholds": {
    "global": 80,
    "files": {
      "/app/models/User.cfc": 90,
      "/app/models/Order.cfc": 95
    }
  }
}

Configuration

Coverage Configuration File

.wheels-coverage.json:

{
  "include": [
    "app/models/**/*.cfc",
    "app/controllers/**/*.cfc"
  ],
  "exclude": [
    "app/models/Legacy.cfc",
    "**/*Test.cfc"
  ],
  "reporters": ["html", "json"],
  "reportDir": "./coverage",
  "thresholds": {
    "global": 80
  },
  "watermarks": {
    "lines": [50, 80],
    "functions": [50, 80],
    "branches": [50, 80],
    "statements": [50, 80]
  }
}

Integration

CI/CD Pipeline

- name: Run tests with coverage
  run: |
    wheels test coverage --format=xml --threshold=80 --fail-on-low
    
- name: Upload coverage
  uses: codecov/codecov-action@v3
  with:
    file: ./coverage/coverage.xml

Git Hooks

.git/hooks/pre-push:

#!/bin/bash
wheels test coverage --threshold=80 --fail-on-low

Badge Generation

wheels test coverage --format=badge > coverage-badge.svg

Analyzing Results

Identify Untested Code

The HTML report highlights:

  • Red: Uncovered lines

  • Yellow: Partially covered branches

  • Green: Fully covered code

Focus Areas

  1. Critical Paths: Ensure high coverage

  2. Complex Logic: Test all branches

  3. Error Handling: Cover edge cases

  4. New Features: Maintain coverage

Best Practices

  1. Set Realistic Goals: Start with achievable thresholds

  2. Incremental Improvement: Gradually increase thresholds

  3. Focus on Quality: 100% coverage doesn't mean bug-free

  4. Test Business Logic: Prioritize critical code

  5. Regular Monitoring: Track coverage trends

Performance Considerations

Coverage collection adds overhead:

  • Slower test execution

  • Increased memory usage

  • Larger test artifacts

Tips:

  • Run coverage in CI/CD, not every test run

  • Use incremental coverage for faster feedback

  • Exclude third-party code

Troubleshooting

Low Coverage

  • Check if tests are actually running

  • Verify include/exclude patterns

  • Look for untested files

Coverage Not Collected

  • Ensure code is instrumented

  • Check file path patterns

  • Verify test execution

Report Generation Failed

  • Check output directory permissions

  • Verify report format support

  • Review error logs

Advanced Usage

Incremental Coverage

# Coverage for changed files only
wheels test coverage --since=HEAD~1

Coverage Trends

# Generate trend data
wheels test coverage --save-baseline

# Compare with baseline
wheels test coverage --compare-baseline

Merge Coverage

# From multiple test runs
wheels test coverage --merge coverage1.json coverage2.json

Notes

  • Coverage data is collected during test execution

  • Some code may be unreachable and shouldn't count

  • Focus on meaningful coverage, not just percentages

  • Different metrics provide different insights

See Also

wheels test

Run Wheels framework tests (core, app, or plugin tests).

Synopsis

wheels test [type] [servername] [options]

Description

The wheels test command runs the built-in Wheels framework test suite. This is different from wheels test run which runs your application's TestBox tests. Use this command to verify framework integrity or test Wheels plugins.

Arguments

Argument
Description
Default

type

Test type: core, app, or plugin

app

servername

CommandBox server name

Current server

Options

Option
Description
Default

--reload

Reload before running tests

true

--debug

Show debug output

false

--format

Output format

simple

--adapter

Test adapter

testbox

--help

Show help information

Test Types

Core Tests

wheels test core
  • Tests Wheels framework functionality

  • Verifies framework integrity

  • Useful after framework updates

App Tests

wheels test app
  • Runs application-level framework tests

  • Tests Wheels configuration

  • Verifies app-specific framework features

Plugin Tests

wheels test plugin
  • Tests installed Wheels plugins

  • Verifies plugin compatibility

  • Checks plugin functionality

Examples

Run app tests (default)

wheels test

Run core framework tests

wheels test core

Run tests on specific server

wheels test app myserver

Run with debug output

wheels test --debug

Skip reload

wheels test --reload=false

Output Example

╔═══════════════════════════════════════════════╗
║           Running Wheels Tests                ║
╚═══════════════════════════════════════════════╝

Test Type: app
Server: default
Reloading: Yes

Initializing test environment...
✓ Environment ready

Running tests...

Model Tests
  ✓ validations work correctly (15ms)
  ✓ associations load properly (23ms)
  ✓ callbacks execute in order (8ms)

Controller Tests
  ✓ filters apply correctly (12ms)
  ✓ caching works as expected (45ms)
  ✓ provides correct formats (5ms)

View Tests
  ✓ helpers render correctly (18ms)
  ✓ partials include properly (9ms)
  ✓ layouts apply correctly (11ms)

Plugin Tests
  ✓ DBMigrate plugin loads (7ms)
  ✓ Scaffold plugin works (22ms)

╔═══════════════════════════════════════════════╗
║              Test Summary                     ║
╚═══════════════════════════════════════════════╝

Total Tests: 11
Passed: 11
Failed: 0
Errors: 0
Time: 173ms

✓ All tests passed!

Framework Test Categories

Model Tests

  • Validations

  • Associations

  • Callbacks

  • Properties

  • Calculations

Controller Tests

  • Filters

  • Caching

  • Provides/formats

  • Redirects

  • Rendering

View Tests

  • Helper functions

  • Form helpers

  • Asset helpers

  • Partials

  • Layouts

Dispatcher Tests

  • Routing

  • URL rewriting

  • Request handling

  • Parameter parsing

Configuration

Test Settings

In /config/settings.cfm:

<cfset set(testEnvironment=true)>
<cfset set(testDataSource="myapp_test")>

Test Database

Create separate test database:

CREATE DATABASE myapp_test;

Debugging Failed Tests

Enable debug mode

wheels test --debug

Check specific test file

Failed: Model Tests > validations work correctly
File: /tests/framework/model/validations.cfc
Line: 45
Expected: true
Actual: false

Common issues

  1. Database not configured: Check test datasource

  2. Reload password wrong: Verify settings

  3. Plugin conflicts: Disable plugins and retest

  4. Cache issues: Clear cache and retry

Continuous Integration

GitHub Actions

- name: Run Wheels tests
  run: |
    box install
    box server start
    wheels test core
    wheels test app

Jenkins

stage('Framework Tests') {
    steps {
        sh 'wheels test core'
        sh 'wheels test app'
    }
}

Custom Framework Tests

Add tests in /tests/framework/:

component extends="wheels.Test" {

    function test_custom_framework_feature() {
        // Test custom framework modification
        actual = customFrameworkMethod();
        assert(actual == expected);
    }

}

Performance Testing

Run with timing:

wheels test --debug | grep "Time:"

Monitor slow tests:

✓ complex query test (523ms) ⚠️ SLOW
✓ simple validation (8ms)

Test Isolation

Tests run in isolation:

  • Separate request for each test

  • Transaction rollback (if enabled)

  • Clean application state

Troubleshooting

Tests won't run

# Check server is running
box server status

# Verify test URL
curl http://localhost:3000/wheels/tests

Reload issues

# Manual reload first
wheels reload

# Then run tests
wheels test --reload=false

Memory issues

# Increase heap size
box server set jvm.heapSize=512
box server restart

Best Practices

  1. Run before deployment

  2. Test after framework updates

  3. Verify plugin compatibility

  4. Use CI/CD integration

  5. Keep test database clean

Difference from TestBox Tests

Feature

wheels test

wheels test run

Purpose

Framework tests

Application tests

Framework

Wheels Test

TestBox

Location

/wheels/tests/

/tests/

Use Case

Framework integrity

App functionality

See Also

  • wheels reload - Reload application

wheels db schema

Export and import database schema definitions.

Synopsis

wheels db schema [command] [options]

Description

The db schema command provides tools for exporting database schemas to files and importing them back. This is useful for version control, documentation, sharing database structures, and setting up new environments.

Subcommands

export

Export database schema to a file

import

Import database schema from a file

diff

Compare two schema files or database states

validate

Validate a schema file without importing

Global Options

--env

  • Type: String

  • Default: development

  • Description: The environment to work with

--datasource

  • Type: String

  • Default: Application default

  • Description: Specific datasource to use

--format

  • Type: String

  • Default: sql

  • Options: sql, json, xml, cfm

  • Description: Output format for schema

Export Command

Synopsis

wheels db schema export [options]

Options

--output

  • Type: String

  • Default: db/schema.sql

  • Description: Output file path

--tables

  • Type: String

  • Default: All tables

  • Description: Comma-separated list of tables to export

--no-data

  • Type: Boolean

  • Default: true

  • Description: Export structure only, no data

--include-drops

  • Type: Boolean

  • Default: false

  • Description: Include DROP statements

--include-indexes

  • Type: Boolean

  • Default: true

  • Description: Include index definitions

--include-constraints

  • Type: Boolean

  • Default: true

  • Description: Include foreign key constraints

Examples

# Export entire schema
wheels db schema export

# Export specific tables
wheels db schema export --tables=user,order,product

# Export as JSON
wheels db schema export --format=json --output=db/schema.json

# Export with DROP statements
wheels db schema export --include-drops --output=db/recreate-schema.sql

Import Command

Synopsis

wheels db schema import [options]

Options

--input

  • Type: String

  • Default: db/schema.sql

  • Description: Input file path

--dry-run

  • Type: Boolean

  • Default: false

  • Description: Preview import without executing

--force

  • Type: Boolean

  • Default: false

  • Description: Drop existing objects before import

--skip-errors

  • Type: Boolean

  • Default: false

  • Description: Continue on errors

Examples

# Import schema
wheels db schema import

# Import from specific file
wheels db schema import --input=db/production-schema.sql

# Preview import
wheels db schema import --dry-run

# Force recreate schema
wheels db schema import --force

Diff Command

Synopsis

wheels db schema diff [source] [target] [options]

Options

--output

  • Type: String

  • Default: Console output

  • Description: Save diff to file

--format

  • Type: String

  • Default: text

  • Options: text, sql, html

  • Description: Diff output format

Examples

# Compare dev and production
wheels db schema diff --env=development --env=production

# Compare schema files
wheels db schema diff db/schema-v1.sql db/schema-v2.sql

# Generate migration SQL
wheels db schema diff --env=development --env=production --format=sql

Use Cases

Version Control

Track schema changes in git:

# Export schema after migrations
wheels dbmigrate latest
wheels db schema export

# Commit schema file
git add db/schema.sql
git commit -m "Update database schema"

Environment Setup

Set up new developer environment:

# Clone repository
git clone repo-url

# Import schema
wheels db schema import

# Run any pending migrations
wheels dbmigrate latest

Database Documentation

Generate schema documentation:

# Export as JSON for documentation tools
wheels db schema export --format=json --output=docs/database-schema.json

# Export with comments
wheels db schema export --include-comments --output=docs/schema-annotated.sql

Continuous Integration

Validate schema in CI:

# Export current schema
wheels db schema export --output=current-schema.sql

# Compare with committed schema
wheels db schema diff db/schema.sql current-schema.sql

Backup and Recovery

Create schema backups:

# Backup before major changes
wheels db schema export --output=backups/schema-$(date +%Y%m%d).sql

# Include drops for full recreate
wheels db schema export --include-drops --output=backups/recreate-$(date +%Y%m%d).sql

Schema File Formats

SQL Format (Default)

-- Generated by CFWheels
-- Date: 2024-01-15 10:30:00

CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(150) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX idx_user_email ON user(email);

JSON Format

{
  "version": "1.0",
  "generated": "2024-01-15T10:30:00Z",
  "tables": {
    "user": {
      "columns": {
        "id": {
          "type": "integer",
          "primaryKey": true,
          "autoIncrement": true
        },
        "username": {
          "type": "string",
          "length": 50,
          "nullable": false
        }
      },
      "indexes": {
        "idx_user_email": {
          "columns": ["email"],
          "unique": true
        }
      }
    }
  }
}

CFM Format

<cfscript>
schema = {
    tables: {
        user: {
            columns: [
                {name: "id", type: "integer", primaryKey: true},
                {name: "username", type: "string", length: 50},
                {name: "email", type: "string", length: 150}
            ],
            indexes: [
                {name: "idx_user_email", columns: ["email"], unique: true}
            ]
        }
    }
};
</cfscript>

Best Practices

1. Regular Exports

Export schema after each migration:

# In deployment script
wheels dbmigrate latest
wheels db schema export
git add db/schema.sql
git commit -m "Update schema after migrations"

2. Environment Comparison

Regularly compare environments:

# Weekly check
wheels db schema diff --env=development --env=production

3. Schema Validation

Validate before deployment:

# In CI pipeline
wheels db schema validate --input=db/schema.sql

4. Backup Strategy

Maintain schema history:

# Before major updates
wheels db schema export --output=db/history/schema-$(git rev-parse --short HEAD).sql

Integration with Migrations

Schema vs Migrations

  • Migrations: Incremental changes

  • Schema: Current state snapshot

Workflow

  1. Run migrations: wheels dbmigrate latest

  2. Export schema: wheels db schema export

  3. Commit both: git add db/migrate/* db/schema.sql

Notes

  • Schema export captures current database state

  • Some database-specific features may not export perfectly

  • Always review exported schemas before importing

  • Use migrations for incremental changes, schemas for full setup

Related Commands

wheels db seed

Populate the database with seed data for development and testing.

Synopsis

wheels db seed [options]

Description

The db seed command populates your database with predefined data sets. This is essential for development environments, testing scenarios, and demo installations. Seed data provides a consistent starting point for application development and testing.

Options

--env

  • Type: String

  • Default: development

  • Description: Environment to seed (development, testing, staging)

--file

  • Type: String

  • Default: db/seeds.cfm

  • Description: Path to seed file or directory

--datasource

  • Type: String

  • Default: Application default

  • Description: Specific datasource to seed

--clean

  • Type: Boolean

  • Default: false

  • Description: Clear existing data before seeding

--only

  • Type: String

  • Default: All seeds

  • Description: Run only specific seed files (comma-separated)

--except

  • Type: String

  • Default: None

  • Description: Skip specific seed files (comma-separated)

--verbose

  • Type: Boolean

  • Default: false

  • Description: Show detailed output during seeding

--dry-run

  • Type: Boolean

  • Default: false

  • Description: Preview seed operations without executing

Examples

Basic seeding

# Run default seeds
wheels db seed

# Seed specific environment
wheels db seed --env=testing

Clean and seed

# Clear data and reseed
wheels db seed --clean

# Clean seed with confirmation
wheels db seed --clean --verbose

Selective seeding

# Run specific seeds
wheels db seed --only=users,products

# Skip certain seeds
wheels db seed --except=large_dataset,temporary_data

Custom seed files

# Use custom seed file
wheels db seed --file=db/seeds/demo_data.cfm

# Use seed directory
wheels db seed --file=db/seeds/development/

Seed File Structure

Basic Seed File (db/seeds.cfm)

<cfscript>
// db/seeds.cfm
component extends="wheels.Seeder" {

    function run() {
        // Create admin user
        user = model("user").create(
            username = "admin",
            email = "admin@example.com",
            password = "password123",
            role = "admin"
        );

        // Create sample categories
        categories = [
            {name: "Electronics", slug: "electronics"},
            {name: "Books", slug: "books"},
            {name: "Clothing", slug: "clothing"}
        ];

        for (category in categories) {
            model("category").create(category);
        }

        // Create sample products
        electronicsCategory = model("category").findOne(where="slug='electronics'");
        
        products = [
            {
                name: "Laptop",
                price: 999.99,
                category_id: electronicsCategory.id,
                in_stock: true
            },
            {
                name: "Smartphone",
                price: 699.99,
                category_id: electronicsCategory.id,
                in_stock: true
            }
        ];

        for (product in products) {
            model("product").create(product);
        }

        announce("Seed data created successfully!");
    }

    function clean() {
        // Clean in reverse order of dependencies
        model("product").deleteAll();
        model("category").deleteAll();
        model("user").deleteAll();
        
        announce("Database cleaned!");
    }

}
</cfscript>

Modular Seed Files

// db/seeds/users.cfm
component extends="wheels.Seeder" {
    
    function run() {
        // Admin users
        createAdminUsers();
        
        // Regular users
        createSampleUsers(count=50);
        
        // Test users
        createTestUsers();
    }
    
    private function createAdminUsers() {
        admins = [
            {username: "admin", email: "admin@example.com", role: "admin"},
            {username: "moderator", email: "mod@example.com", role: "moderator"}
        ];
        
        for (admin in admins) {
            admin.password = hash("password123");
            model("user").create(admin);
        }
    }
    
    private function createSampleUsers(required numeric count) {
        for (i = 1; i <= arguments.count; i++) {
            model("user").create(
                username = "user#i#",
                email = "user#i#@example.com",
                password = hash("password123"),
                created_at = dateAdd("d", -randRange(1, 365), now())
            );
        }
    }
    
}

Use Cases

Development Environment Setup

Create consistent development data:

# Reset and seed development database
wheels dbmigrate reset --remigrate
wheels db seed --clean

Testing Data

Prepare test database:

# Seed test environment
wheels db seed --env=testing --clean

# Run tests
wheels test run

Demo Data

Create demonstration data:

# Load demo dataset
wheels db seed --file=db/seeds/demo.cfm --clean

Performance Testing

Generate large datasets:

# Create performance test data
wheels db seed --file=db/seeds/performance_test.cfm

Advanced Seeding Patterns

Faker Integration

component extends="wheels.Seeder" {
    
    function run() {
        faker = new lib.Faker();
        
        // Generate realistic data
        for (i = 1; i <= 100; i++) {
            model("customer").create(
                first_name = faker.firstName(),
                last_name = faker.lastName(),
                email = faker.email(),
                phone = faker.phoneNumber(),
                address = faker.streetAddress(),
                city = faker.city(),
                state = faker.state(),
                zip = faker.zipCode()
            );
        }
    }
    
}

Relationship Seeding

component extends="wheels.Seeder" {
    
    function run() {
        // Create users
        users = [];
        for (i = 1; i <= 10; i++) {
            users.append(model("user").create(
                username = "user#i#",
                email = "user#i#@example.com"
            ));
        }
        
        // Create posts for each user
        for (user in users) {
            postCount = randRange(5, 15);
            for (j = 1; j <= postCount; j++) {
                post = model("post").create(
                    user_id = user.id,
                    title = "Post #j# by #user.username#",
                    content = generateContent(),
                    published_at = dateAdd("d", -randRange(1, 30), now())
                );
                
                // Add comments
                addCommentsToPost(post, users);
            }
        }
    }
    
    private function addCommentsToPost(post, users) {
        commentCount = randRange(0, 10);
        for (i = 1; i <= commentCount; i++) {
            randomUser = users[randRange(1, arrayLen(users))];
            model("comment").create(
                post_id = post.id,
                user_id = randomUser.id,
                content = "Comment #i# on post",
                created_at = dateAdd("h", i, post.published_at)
            );
        }
    }
    
}

Conditional Seeding

component extends="wheels.Seeder" {
    
    function run() {
        // Only seed if empty
        if (model("user").count() == 0) {
            seedUsers();
        }
        
        // Environment-specific seeding
        if (application.environment == "development") {
            seedDevelopmentData();
        } else if (application.environment == "staging") {
            seedStagingData();
        }
    }
    
}

Best Practices

1. Idempotent Seeds

Make seeds safe to run multiple times:

function run() {
    // Check before creating
    if (!model("user").exists(username="admin")) {
        model("user").create(
            username = "admin",
            email = "admin@example.com"
        );
    }
}

2. Use Transactions

Wrap seeds in transactions:

function run() {
    transaction {
        try {
            seedUsers();
            seedProducts();
            seedOrders();
        } catch (any e) {
            transaction action="rollback";
            throw(e);
        }
    }
}

3. Organize by Domain

Structure seeds logically:

db/seeds/
  ├── 01_users.cfm
  ├── 02_products.cfm
  ├── 03_orders.cfm
  └── 04_analytics.cfm

4. Document Seeds

Add clear documentation:

/**
 * Seeds initial product catalog
 * Creates: 5 categories, 50 products
 * Dependencies: None
 * Runtime: ~2 seconds
 */
function run() {
    // Seed implementation
}

Error Handling

Validation Errors

function run() {
    try {
        user = model("user").create(data);
        if (user.hasErrors()) {
            announce("Failed to create user: #user.allErrors()#");
        }
    } catch (any e) {
        announce("Error: #e.message#", "error");
    }
}

Dependency Handling

function run() {
    // Check dependencies
    if (model("category").count() == 0) {
        throw("Categories must be seeded first!");
    }
    
    // Continue with seeding
}

Notes

  • Seed files are typically not run in production

  • Always use transactions for data integrity

  • Consider performance for large seed operations

  • Keep seed data realistic and useful

Related Commands

wheels test debug

Debug test execution with detailed diagnostics and troubleshooting tools.

Synopsis

wheels test debug [spec] [options]

Description

The wheels test debug command provides advanced debugging capabilities for your test suite. It helps identify why tests are failing, diagnose test environment issues, and provides detailed execution traces for troubleshooting complex test problems.

Arguments

Argument
Description
Default

spec

Specific test spec to debug

All tests

Options

Option
Description
Default

--bundles

Test bundles to debug

All bundles

--labels

Filter by test labels

None

--breakpoint

Set breakpoint at test

None

--step

Step through test execution

false

--trace

Show execution trace

false

--verbose

Verbose output level (1-3)

1

--slow

Highlight slow tests (ms)

1000

--dump-context

Dump test context

false

--inspect

Enable remote debugging

false

--port

Debug port for inspection

9229

--pause-on-failure

Pause when test fails

false

--replay

Replay failed tests

false

--help

Show help information

Examples

Debug specific test

wheels test debug UserModelTest

Debug with execution trace

wheels test debug --trace

Step through test execution

wheels test debug UserModelTest.testValidation --step

Debug with breakpoints

wheels test debug --breakpoint=UserModelTest.testLogin:15

Enable remote debugging

wheels test debug --inspect --port=9229

Debug slow tests

wheels test debug --slow=500 --verbose=2

Debug Output

Basic Debug Info

🔍 Test Debug Session Started
================================

Environment: testing
Debug Level: 1
Test Framework: TestBox 5.0.0
CFML Engine: Lucee 5.3.9.141

Running: UserModelTest.testValidation
Status: RUNNING

[DEBUG] Setting up test case...
[DEBUG] Creating test user instance
[DEBUG] Validating empty user
[DEBUG] Assertion: user.hasErrors() = true ✓
[DEBUG] Test completed in 45ms

Verbose Trace Output

With --trace --verbose=3:

🔍 Test Execution Trace
======================

▶ UserModelTest.setup()
  └─ [0.5ms] Creating test database transaction
  └─ [1.2ms] Loading test fixtures
  └─ [0.3ms] Initializing test context

▶ UserModelTest.testValidation()
  ├─ [0.1ms] var user = model("User").new()
  │   └─ [2.1ms] Model instantiation
  │   └─ [0.5ms] Property initialization
  ├─ [0.2ms] user.validate()
  │   └─ [5.3ms] Running validations
  │   ├─ [1.2ms] Checking required fields
  │   ├─ [2.1ms] Email format validation
  │   └─ [2.0ms] Custom validations
  ├─ [0.1ms] expect(user.hasErrors()).toBe(true)
  │   └─ [0.3ms] Assertion passed ✓
  └─ [0.1ms] Test completed

Total Time: 10.2ms
Memory Used: 2.3MB

Interactive Debugging

Step Mode

With --step:

▶ Entering step mode for UserModelTest.testLogin

[1] user = model("User").findOne(where="email='test@example.com'")
    > (n)ext, (s)tep into, (c)ontinue, (v)ariables, (q)uit: v

Variables:
- arguments: {}
- local: { user: [undefined] }
- this: UserModelTest instance

    > n

[2] expect(user.authenticate("password123")).toBe(true)
    > v

Variables:
- arguments: {}
- local: { user: User instance {id: 1, email: "test@example.com"} }

    > s

  [2.1] Entering: user.authenticate("password123")
       Parameters: { password: "password123" }

Breakpoints

Set breakpoints in code:

// In test file
function testComplexLogic() {
    var result = complexCalculation(data);
    
    debugBreak(); // Execution pauses here
    
    expect(result).toBe(expectedValue);
}

Or via command line:

wheels test debug --breakpoint=OrderTest.testCalculateTotal:25

Test Context Inspection

Dump Test Context

With --dump-context:

Test Context Dump
================

Test: UserModelTest.testPermissions
Phase: Execution

Application Scope:
- wheels.version: 2.5.0
- wheels.environment: testing
- Custom settings: { ... }

Request Scope:
- cgi.request_method: "GET"
- url: { testMethod: "testPermissions" }

Test Data:
- Fixtures loaded: users, roles, permissions
- Test user: { id: 999, email: "test@example.com" }
- Database state: Transaction active

Component State:
- UserModelTest properties: { ... }
- Inherited properties: { ... }

Performance Analysis

Slow Test Detection

With --slow=500:

⚠️ Slow Tests Detected
=====================

1. OrderModelTest.testLargeOrderProcessing - 2,345ms 🐌
   - Database queries: 45 (1,234ms)
   - Model operations: 892ms
   - Assertions: 219ms

2. UserControllerTest.testBulkImport - 1,567ms 🐌
   - File I/O: 623ms
   - Validation: 512ms
   - Database inserts: 432ms

3. ReportTest.testGenerateYearlyReport - 987ms ⚠️
   - Data aggregation: 654ms
   - PDF generation: 333ms

Remote Debugging

Enable Inspector

wheels test debug --inspect

Connect with Chrome DevTools:

  1. Open Chrome/Edge

  2. Navigate to chrome://inspect

  3. Click "Configure" and add localhost:9229

  4. Click "inspect" on the target

Debug Protocol

wheels test debug --inspect-brk --port=9230
  • --inspect: Enable debugging

  • --inspect-brk: Break on first line

  • Custom port for multiple sessions

Failure Analysis

Pause on Failure

With --pause-on-failure:

✗ Test Failed: UserModelTest.testUniqueEmail

Test paused at failure point.

Failure Details:
- Expected: true
- Actual: false
- Location: UserModelTest.cfc:45

Debug Options:
(i) Inspect variables
(s) Show stack trace
(d) Dump database state
(r) Retry test
(c) Continue
(q) Quit

> i

Local Variables:
- user1: User { email: "test@example.com", id: 1 }
- user2: User { email: "test@example.com", errors: ["Email already exists"] }

Stack Trace Analysis

Stack Trace:
-----------
1. TestBox.expectation.toBe() at TestBox/system/Expectation.cfc:123
2. UserModelTest.testUniqueEmail() at tests/models/UserModelTest.cfc:45
3. TestBox.runTest() at TestBox/system/BaseSpec.cfc:456
4. Model.validate() at wheels/Model.cfc:789
5. Model.validatesUniquenessOf() at wheels/Model.cfc:1234

Test Replay

Replay Failed Tests

wheels test debug --replay

Replays last failed tests with debug info:

Replaying 3 failed tests from last run...

1/3 UserModelTest.testValidation
    - Original failure: Assertion failed at line 23
    - Replay status: PASSED ✓
    - Possible flaky test

2/3 OrderControllerTest.testCheckout
    - Original failure: Database connection timeout
    - Replay status: FAILED ✗
    - Consistent failure

Configuration

Debug Configuration

.wheels-test-debug.json:

{
  "debug": {
    "defaultLevel": 1,
    "slowThreshold": 1000,
    "breakpoints": [
      "UserModelTest.testComplexScenario:45",
      "OrderTest.testEdgeCase:78"
    ],
    "trace": {
      "includeFramework": false,
      "maxDepth": 10
    },
    "output": {
      "colors": true,
      "timestamps": true,
      "saveToFile": "./debug.log"
    }
  }
}

Debugging Strategies

1. Isolate Failing Test

# Run only the failing test
wheels test debug UserModelTest.testValidation --trace

2. Check Test Environment

# Dump environment and context
wheels test debug --dump-context > test-context.txt

3. Step Through Execution

# Interactive debugging
wheels test debug FailingTest --step --pause-on-failure

4. Compare Working vs Failing

# Debug working test
wheels test debug WorkingTest --trace > working.log

# Debug failing test
wheels test debug FailingTest --trace > failing.log

# Compare outputs
diff working.log failing.log

Common Issues

Test Pollution

Debug test isolation:

wheels test debug --trace --verbose=3 | grep -E "(setup|teardown|transaction)"

Race Conditions

Debug timing issues:

wheels test debug --slow=100 --trace

Database State

wheels test debug --dump-context | grep -A 20 "Database state"

Best Practices

  1. Start Simple: Use basic debug before advanced options

  2. Isolate Issues: Debug one test at a time

  3. Use Breakpoints: Strategic breakpoints save time

  4. Check Environment: Ensure test environment is correct

  5. Save Debug Logs: Keep logs for complex issues

Notes

  • Debug mode affects test performance

  • Some features require specific CFML engine support

  • Remote debugging requires network access

  • Verbose output can be overwhelming - filter as needed

See Also

wheels env setup

Setup a new environment configuration for your Wheels application.

Synopsis

wheels env setup [name] [options]

Description

The wheels env setup command creates and configures new environments for your Wheels application. It generates environment-specific configuration files, database settings, and initializes the environment structure.

Arguments

Argument
Description
Default

name

Environment name (e.g., staging, qa, production)

Required

Options

Option
Description
Default

--base

Base environment to copy from

development

--database

Database name

wheels_[name]

--datasource

CF datasource name

wheels_[name]

--debug

Enable debug mode

false

--cache

Enable caching

Based on name

--reload-password

Password for reload

Random

--skip-database

Skip database creation

false

--force

Overwrite existing environment

false

--help

Show help information

Examples

Setup basic environment

wheels env setup staging

Setup with custom database

wheels env setup qa --database=wheels_qa_db --datasource=qa_datasource

Copy from production settings

wheels env setup staging --base=production

Setup with specific options

wheels env setup production --debug=false --cache=true --reload-password=secret123

Skip database setup

wheels env setup testing --skip-database

What It Does

  1. Creates Configuration Directory:

    /config/[environment]/
    └── settings.cfm
  2. Generates Settings File:

    • Database configuration

    • Environment-specific settings

    • Debug and cache options

    • Security settings

  3. Sets Up Database (unless skipped):

    • Creates database

    • Configures datasource

    • Tests connection

  4. Updates Environment Registry:

    • Adds to available environments

    • Sets up environment detection

Generated Configuration

Example config/staging/settings.cfm:

<cfscript>
// Environment: staging
// Generated: 2024-01-15 10:30:45

// Database settings
set(dataSourceName="wheels_staging");

// Environment settings
set(environment="staging");
set(showDebugInformation=true);
set(showErrorInformation=true);

// Caching
set(cacheFileChecking=false);
set(cacheImages=false);
set(cacheModelInitialization=false);
set(cacheControllerInitialization=false);
set(cacheRoutes=false);
set(cacheActions=false);
set(cachePages=false);
set(cachePartials=false);
set(cacheQueries=false);

// Security
set(reloadPassword="generated_secure_password");

// URLs
set(urlRewriting="partial");

// Custom settings for staging
set(sendEmailOnError=true);
set(errorEmailAddress="dev-team@example.com");
</cfscript>

Environment Types

Development

  • Full debugging

  • No caching

  • Detailed errors

  • Hot reload

Testing

  • Test database

  • Debug enabled

  • Isolated data

  • Fast cleanup

Staging

  • Production-like

  • Some debugging

  • Performance testing

  • Pre-production validation

Production

  • No debugging

  • Full caching

  • Error handling

  • Optimized performance

Custom

Create specialized environments:

wheels env setup performance-testing --base=production --cache=false

Database Configuration

Automatic Setup

wheels env setup staging
# Creates: wheels_staging database
# Datasource: wheels_staging

Custom Database

wheels env setup staging \
  --database=staging_db \
  --datasource=myapp_staging

Database URL

wheels env setup production \
  --database-url="mysql://user:pass@host:3306/db"

Environment Variables

The command sets up support for:

# .env.staging
WHEELS_ENV=staging
WHEELS_DATASOURCE=wheels_staging
WHEELS_DEBUG=true
WHEELS_CACHE=false
DATABASE_URL=mysql://localhost/wheels_staging

Configuration Inheritance

Environments can inherit settings:

// config/staging/settings.cfm
<cfinclude template="../production/settings.cfm">

// Override specific settings
set(showDebugInformation=true);
set(cacheQueries=false);

Validation

After setup, the command validates:

  1. Configuration file syntax

  2. Database connectivity

  3. Directory permissions

  4. Environment detection

Environment Detection

Configure how environment is detected:

// config/environment.cfm
if (cgi.server_name contains "staging") {
    set(environment="staging");
} else if (cgi.server_name contains "qa") {
    set(environment="qa");
} else {
    set(environment="production");
}

Best Practices

  1. Naming Convention: Use clear, consistent names

  2. Base Selection: Choose appropriate base environment

  3. Security: Use strong reload passwords

  4. Documentation: Document environment purposes

  5. Testing: Test configuration before use

Advanced Configuration

Multiple Databases

wheels env setup reporting \
  --database=wheels_reporting \
  --read-database=wheels_replica

Load Balancing

wheels env setup production \
  --servers=web1,web2,web3 \
  --load-balancer=nginx

Feature Flags

// In settings.cfm
set(features={
    newCheckout: true,
    betaAPI: false,
    debugToolbar: true
});

Troubleshooting

Database Creation Failed

  • Check database permissions

  • Verify connection settings

  • Use --skip-database and create manually

Configuration Errors

  • Check syntax in settings.cfm

  • Verify file permissions

  • Review error logs

Environment Not Detected

  • Check environment.cfm logic

  • Verify server variables

  • Test detection rules

Migration

From Existing Environment

# Export existing config
wheels env export production > prod-config.json

# Import to new environment
wheels env setup staging --from-config=prod-config.json

Use Cases

  1. Multi-Stage Pipeline: dev → staging → production

  2. Feature Testing: Isolated feature environments

  3. Performance Testing: Dedicated performance environment

  4. Client Demos: Separate demo environments

  5. A/B Testing: Multiple production variants

Notes

  • Environment names should be lowercase

  • Avoid spaces in environment names

  • Each environment needs unique database

  • Restart application after setup

  • Test thoroughly before using

See Also

wheels config set

Set configuration values for your Wheels application.

Synopsis

wheels config set [key] [value] [options]

Description

The wheels config set command updates configuration settings in your Wheels application. It can modify settings in configuration files, set environment-specific values, and manage runtime configurations.

Arguments

Argument
Description
Required

key

Configuration key to set

Yes

value

Value to set

Yes (unless --delete)

Options

Option
Description
Default

--environment

Target environment

Current

--global

Set globally across all environments

false

--file

Configuration file to update

Auto-detect

--type

Value type (string, number, boolean, json)

Auto-detect

--encrypt

Encrypt sensitive values

Auto for passwords

--delete

Delete the configuration key

false

--force

Overwrite without confirmation

false

--help

Show help information

Examples

Set basic configuration

wheels config set dataSourceName wheels_production

Set with specific type

wheels config set cacheQueries true --type=boolean
wheels config set sessionTimeout 3600 --type=number

Set for specific environment

wheels config set showDebugInformation false --environment=production

Set complex value

wheels config set cacheSettings '{"queries":true,"pages":false}' --type=json

Delete configuration

wheels config set oldSetting --delete

Set encrypted value

wheels config set apiKey sk_live_abc123 --encrypt

Configuration Types

String Values

wheels config set appName "My Wheels App"
wheels config set emailFrom "noreply@example.com"

Boolean Values

wheels config set showDebugInformation true
wheels config set cacheQueries false

Numeric Values

wheels config set sessionTimeout 1800
wheels config set maxUploadSize 10485760

JSON/Complex Values

wheels config set mailSettings '{"server":"smtp.example.com","port":587}'
wheels config set allowedDomains '["example.com","app.example.com"]'

Where Settings Are Saved

Environment-Specific

Default location: /config/[environment]/settings.cfm

// Added to /config/production/settings.cfm
set(dataSourceName="wheels_production");

Global Settings

Location: /config/settings.cfm

wheels config set defaultLayout "main" --global

Environment Variables

For system-level settings:

wheels config set DATABASE_URL "mysql://..." --env-var

Value Type Detection

The command auto-detects types:

  • true/false → boolean

  • Numbers → numeric

  • JSON syntax → struct/array

  • Default → string

Override with --type:

wheels config set port "8080" --type=string

Sensitive Values

Automatic Encryption

These patterns trigger encryption:

  • *password*

  • *secret*

  • *key*

  • *token*

Manual Encryption

wheels config set customSecret "value" --encrypt

Encrypted Storage

// Stored as:
set(apiKey=decrypt("U2FsdGVkX1+..."));

Validation

Before setting, validates:

  1. Key name syntax

  2. Value type compatibility

  3. Environment existence

  4. File write permissions

Interactive Mode

For sensitive values:

wheels config set reloadPassword
# Enter value (hidden): ****
# Confirm value: ****

Batch Operations

From File

# config.txt
dataSourceName=wheels_prod
cacheQueries=true
sessionTimeout=3600

wheels config set --from-file=config.txt

Multiple Values

wheels config set \
  dataSourceName=wheels_prod \
  cacheQueries=true \
  sessionTimeout=3600

Configuration Precedence

Order of precedence (highest to lowest):

  1. Runtime set() calls

  2. Environment variables

  3. Environment-specific settings

  4. Global settings

  5. Framework defaults

Rollback

Create Backup

wheels config set dataSourceName wheels_new --backup
# Creates: .wheels-config-backup-20240115-103045

Restore

wheels config restore --from=.wheels-config-backup-20240115-103045

Special Keys

Reserved Keys

Some keys have special behavior:

  • environment - Switches environment

  • reloadPassword - Always encrypted

  • dataSourcePassword - Hidden in output

Computed Keys

Some settings affect others:

wheels config set environment production
# Also updates: debug settings, cache settings

Environment Variables

Set as Environment Variable

wheels config set WHEELS_DATASOURCE wheels_prod --env-var

Export Format

wheels config set --export-env > .env

Validation Rules

Key Naming

  • Alphanumeric and underscores

  • No spaces or special characters

  • Case-sensitive

Value Constraints

# Validates port range
wheels config set port 80000 --type=number
# Error: Port must be between 1-65535

# Validates boolean
wheels config set cacheQueries maybe --type=boolean
# Error: Value must be true or false

Best Practices

  1. Use Correct Types: Specify type for clarity

  2. Environment-Specific: Don't set production values globally

  3. Encrypt Secrets: Always encrypt sensitive data

  4. Backup First: Create backups before changes

  5. Document Changes: Add comments in config files

Advanced Usage

Conditional Setting

# Set only if not exists
wheels config set apiUrl "https://api.example.com" --if-not-exists

# Set only if current value matches
wheels config set cacheQueries true --if-value=false

Template Variables

wheels config set dbName "wheels_${ENVIRONMENT}" --parse-template

Troubleshooting

Permission Denied

  • Check file write permissions

  • Run with appropriate user

  • Verify directory ownership

Setting Not Taking Effect

  • Restart application

  • Clear caches

  • Check precedence order

Invalid Value

  • Verify type compatibility

  • Check for typos

  • Review validation rules

Integration

CI/CD Pipeline

- name: Configure production
  run: |
    wheels config set environment production
    wheels config set dataSourceName ${{ secrets.DB_NAME }}
    wheels config set reloadPassword ${{ secrets.RELOAD_PASS }}

Docker

RUN wheels config set dataSourceName ${DB_NAME} \
    && wheels config set cacheQueries true

Notes

  • Some settings require application restart

  • Encrypted values can't be read back

  • Changes are logged for audit

  • Use environment variables for containers

See Also

wheels config list

List all configuration settings for your Wheels application.

Synopsis

wheels config list [options]

Description

The wheels config list command displays all configuration settings for your Wheels application. It shows current values, defaults, and helps you understand your application's configuration state.

Options

Option
Description
Default

--filter

Filter settings by name or pattern

Show all

--category

Filter by category (database, cache, security, etc.)

All

--format

Output format (table, json, yaml, env)

table

--show-defaults

Include default values

false

--show-source

Show where setting is defined

false

--environment

Show for specific environment

Current

--verbose

Show detailed information

false

--help

Show help information

Examples

List all settings

wheels config list

Filter by pattern

wheels config list --filter=cache
wheels config list --filter="database*"

Show specific category

wheels config list --category=security

Export as JSON

wheels config list --format=json > config.json

Show with sources

wheels config list --show-source --show-defaults

Environment-specific

wheels config list --environment=production

Output Example

Basic Output

Wheels Configuration Settings
============================

Setting                          Value
-------------------------------- --------------------------------
dataSourceName                   wheels_dev
environment                      development
reloadPassword                   ********
showDebugInformation            true
showErrorInformation            true
cacheFileChecking               false
cacheQueries                    false
cacheActions                    false
urlRewriting                    partial
assetQueryString                true
assetPaths                      true

Verbose Output

Wheels Configuration Settings
============================

Database Settings
-----------------
dataSourceName                   wheels_dev
  Source: /config/development/settings.cfm
  Default: wheels
  Type: string
  
dataSourceUserName              [not set]
  Source: default
  Default: 
  Type: string

Cache Settings
--------------
cacheQueries                    false
  Source: /config/development/settings.cfm
  Default: false
  Type: boolean
  Description: Cache database query results

Configuration Categories

Database

  • dataSourceName - Primary datasource

  • dataSourceUserName - Database username

  • dataSourcePassword - Database password

  • database - Database name

Cache

  • cacheQueries - Cache query results

  • cacheActions - Cache action output

  • cachePages - Cache full pages

  • cachePartials - Cache partial views

  • cacheFileChecking - Check file modifications

Security

  • reloadPassword - Application reload password

  • showDebugInformation - Show debug info

  • showErrorInformation - Show error details

  • encryptionKey - Data encryption key

  • sessionTimeout - Session duration

URLs/Routing

  • urlRewriting - URL rewriting mode

  • assetQueryString - Add version to assets

  • assetPaths - Use asset paths

Development

  • environment - Current environment

  • hostName - Application hostname

  • deletePluginDirectories - Remove plugin dirs

  • overwritePlugins - Allow plugin overwrites

Filtering Options

By Pattern

# All cache settings
wheels config list --filter=cache*

# Settings containing "database"
wheels config list --filter=*database*

# Specific setting
wheels config list --filter=reloadPassword

By Category

# Database settings only
wheels config list --category=database

# Security settings
wheels config list --category=security

# Multiple categories
wheels config list --category=database,cache

Output Formats

Table (Default)

Human-readable table format

JSON

wheels config list --format=json
{
  "settings": {
    "dataSourceName": "wheels_dev",
    "environment": "development",
    "cacheQueries": false
  }
}

YAML

wheels config list --format=yaml
settings:
  dataSourceName: wheels_dev
  environment: development
  cacheQueries: false

Environment Variables

wheels config list --format=env
WHEELS_DATASOURCE=wheels_dev
WHEELS_ENVIRONMENT=development
WHEELS_CACHE_QUERIES=false

Source Information

When using --show-source:

Source Types

  • File: Specific configuration file

  • Environment: Environment variable

  • Default: Framework default

  • Plugin: Set by plugin

  • Runtime: Set during runtime

Source Priority

  1. Runtime settings (highest)

  2. Environment variables

  3. Environment-specific config

  4. Base configuration

  5. Plugin settings

  6. Framework defaults (lowest)

Advanced Usage

Compare Environments

# Compare dev and production
wheels config list --environment=development > dev.json
wheels config list --environment=production > prod.json
diff dev.json prod.json

Audit Configuration

# Find non-default settings
wheels config list --show-defaults | grep -v "default"

# Find security issues
wheels config list --category=security --check

Export for Documentation

# Markdown format
wheels config list --format=markdown > CONFIG.md

# Include descriptions
wheels config list --verbose --format=markdown

Integration

CI/CD Usage

# Verify required settings
required="dataSourceName,reloadPassword"
wheels config list --format=json | jq --arg req "$required" '
  .settings | with_entries(select(.key | IN($req | split(","))))
'

Monitoring

# Check for changes
wheels config list --format=json > config-current.json
diff config-baseline.json config-current.json

Special Values

Hidden Values

Sensitive settings show as asterisks:

  • Passwords: ********

  • Keys: ****...****

  • Secrets: [hidden]

Complex Values

  • Arrays: ["item1", "item2"]

  • Structs: {key: "value"}

  • Functions: [function]

Best Practices

  1. Regular Audits: Check configuration regularly

  2. Document Changes: Track setting modifications

  3. Environment Parity: Keep environments similar

  4. Secure Secrets: Don't expose sensitive data

  5. Version Control: Track configuration files

Troubleshooting

Missing Settings

  • Check environment-specific files

  • Verify file permissions

  • Look for syntax errors

Incorrect Values

  • Check source precedence

  • Verify environment variables

  • Review recent changes

Notes

  • Some settings require restart to take effect

  • Sensitive values are automatically hidden

  • Custom settings from plugins included

  • Performance impact minimal

See Also

wheels env list

List all available environments for your Wheels application.

Synopsis

wheels env list [options]

Description

The wheels env list command displays all configured environments in your Wheels application. It shows environment details, current active environment, and configuration status.

Options

Option
Description
Default

--format

Output format (table, json, yaml)

table

--verbose

Show detailed configuration

false

--check

Validate environment configurations

false

--filter

Filter by environment type

All

--sort

Sort by (name, type, modified)

name

--help

Show help information

Examples

List all environments

wheels env list

Show detailed information

wheels env list --verbose

Output as JSON

wheels env list --format=json

Check environment validity

wheels env list --check

Filter production environments

wheels env list --filter=production

Output Example

Basic Output

Available Environments
=====================

  NAME          TYPE         DATABASE           STATUS
  development * Development  wheels_dev         ✓ Active
  testing       Testing      wheels_test        ✓ Valid
  staging       Staging      wheels_staging     ✓ Valid
  production    Production   wheels_prod        ✓ Valid
  qa            Custom       wheels_qa          ⚠ Issues

* = Current environment

Verbose Output

Available Environments
=====================

development * [Active]
  Type:        Development
  Database:    wheels_dev
  Datasource:  wheels_development
  Debug:       Enabled
  Cache:       Disabled
  Config:      /config/development/settings.cfm
  Modified:    2024-01-10 14:23:45
  
testing
  Type:        Testing
  Database:    wheels_test
  Datasource:  wheels_testing
  Debug:       Enabled
  Cache:       Disabled
  Config:      /config/testing/settings.cfm
  Modified:    2024-01-08 09:15:22

staging
  Type:        Staging
  Database:    wheels_staging
  Datasource:  wheels_staging
  Debug:       Partial
  Cache:       Enabled
  Config:      /config/staging/settings.cfm
  Modified:    2024-01-12 16:45:00

JSON Output Format

{
  "environments": [
    {
      "name": "development",
      "type": "Development",
      "active": true,
      "database": "wheels_dev",
      "datasource": "wheels_development",
      "debug": true,
      "cache": false,
      "configPath": "/config/development/settings.cfm",
      "lastModified": "2024-01-10T14:23:45Z",
      "status": "valid"
    },
    {
      "name": "production",
      "type": "Production",
      "active": false,
      "database": "wheels_prod",
      "datasource": "wheels_production",
      "debug": false,
      "cache": true,
      "configPath": "/config/production/settings.cfm",
      "lastModified": "2024-01-12T16:45:00Z",
      "status": "valid"
    }
  ],
  "current": "development",
  "total": 5
}

Environment Status

Status Indicators

  • ✓ Valid - Configuration is valid and working

  • ✓ Active - Currently active environment

  • ⚠ Issues - Configuration issues detected

  • ✗ Invalid - Configuration errors

Validation Checks

When using --check:

  1. Configuration file exists

  2. Syntax is valid

  3. Database connection works

  4. Required settings present

Environment Types

Standard Types

  • Development: Local development

  • Testing: Automated testing

  • Staging: Pre-production

  • Production: Live environment

Custom Types

  • User-defined environments

  • Special purpose configs

  • Client-specific setups

Filtering Options

By Type

# Production environments only
wheels env list --filter=production

# Development environments
wheels env list --filter=development

By Status

# Valid environments only
wheels env list --filter=valid

# Environments with issues
wheels env list --filter=issues

By Pattern

# Environments containing "prod"
wheels env list --filter="*prod*"

Sorting Options

By Name

wheels env list --sort=name

By Type

wheels env list --sort=type

By Last Modified

wheels env list --sort=modified

Integration

Script Usage

# Get current environment
current=$(wheels env list --format=json | jq -r '.current')

# List all environment names
wheels env list --format=json | jq -r '.environments[].name'

CI/CD Usage

# Verify environment exists
if wheels env list | grep -q "staging"; then
    wheels env switch staging
fi

Environment Details

When using --verbose, shows:

  1. Configuration:

    • Config file path

    • Last modified date

    • File size

  2. Database:

    • Database name

    • Datasource name

    • Connection status

  3. Settings:

    • Debug mode

    • Cache settings

    • Custom configurations

  4. Validation:

    • Syntax check

    • Connection test

    • Dependencies

Troubleshooting

No Environments Listed

  • Check /config/ directory

  • Verify environment.cfm exists

  • Run wheels env setup to create

Invalid Environment

  • Check configuration syntax

  • Verify database credentials

  • Test database connection

Missing Current Environment

  • Check WHEELS_ENV variable

  • Verify environment.cfm logic

  • Set environment explicitly

Export Capabilities

Export Configuration

# Export all environments
wheels env list --format=json > environments.json

# Export for documentation
wheels env list --format=markdown > ENVIRONMENTS.md

Environment Comparison

# Compare environments
wheels env list --compare=development,production

Best Practices

  1. Regular Checks: Validate environments periodically

  2. Documentation: Keep environment purposes clear

  3. Consistency: Use consistent naming

  4. Cleanup: Remove unused environments

  5. Security: Don't expose production details

Notes

  • Current environment marked with asterisk (*)

  • Invalid environments shown but marked

  • Verbose mode may expose sensitive data

  • JSON format useful for automation

See Also

wheels config env

Manage environment-specific configuration for your Wheels application.

Synopsis

wheels config env [action] [environment] [options]

Description

The wheels config env command provides specialized tools for managing environment-specific configurations. It helps you create, compare, sync, and validate configurations across different environments.

Actions

Action
Description

show

Display environment configuration

compare

Compare configurations between environments

sync

Synchronize settings between environments

validate

Validate environment configuration

export

Export environment configuration

import

Import environment configuration

Arguments

Argument
Description
Required

action

Action to perform

Yes

environment

Target environment name

Depends on action

Options

Option
Description
Default

--format

Output format (table, json, yaml, diff)

table

--output

Output file path

Console

--filter

Filter settings by pattern

Show all

--include-defaults

Include default values

false

--safe

Hide sensitive values

true

--force

Force operation without confirmation

false

--help

Show help information

Examples

Show environment config

wheels config env show production

Compare environments

wheels config env compare development production

Sync configurations

wheels config env sync development staging

Validate configuration

wheels config env validate production

Export configuration

wheels config env export production --format=json > prod-config.json

Show Environment Configuration

Display all settings for an environment:

wheels config env show staging

Output:

Environment: staging
Configuration File: /config/staging/settings.cfm
Last Modified: 2024-01-15 10:30:45

Settings:
---------
dataSourceName: wheels_staging
environment: staging
showDebugInformation: true
showErrorInformation: true
cacheQueries: false
cacheActions: true
urlRewriting: partial

Database:
---------
Server: localhost
Database: wheels_staging
Port: 3306

Features:
---------
Debug: Enabled
Cache: Partial
Reload: Protected

Compare Environments

Basic Comparison

wheels config env compare development production

Output:

Comparing: development → production

Setting                 Development          Production
--------------------- ------------------- -------------------
environment           development         production
dataSourceName        wheels_dev          wheels_prod
showDebugInformation  true               false
showErrorInformation  true               false
cacheQueries          false              true
cacheActions          false              true

Differences: 6
Only in development: 2
Only in production: 1

Detailed Diff

wheels config env compare development production --format=diff

Output:

--- development
+++ production
@@ -1,6 +1,6 @@
-environment=development
-dataSourceName=wheels_dev
-showDebugInformation=true
-showErrorInformation=true
-cacheQueries=false
-cacheActions=false
+environment=production
+dataSourceName=wheels_prod
+showDebugInformation=false
+showErrorInformation=false
+cacheQueries=true
+cacheActions=true

Synchronize Environments

Copy Settings

wheels config env sync production staging

Prompts:

Sync configuration from production to staging?

Settings to copy:
- cacheQueries: false → true
- cacheActions: false → true
- sessionTimeout: 1800 → 3600

Settings to preserve:
- dataSourceName: wheels_staging
- environment: staging

Continue? (y/N)

Selective Sync

wheels config env sync production staging --filter=cache*

Safe Sync

wheels config env sync production staging --safe
# Excludes: passwords, keys, datasources

Validate Configuration

Full Validation

wheels config env validate production

Output:

Validating: production

✓ Configuration file exists
✓ Syntax is valid
✓ Required settings present
✓ Database connection successful
⚠ Warning: Debug information enabled
✗ Error: Missing encryption key

Status: FAILED (1 error, 1 warning)

Quick Check

wheels config env validate all

Shows validation summary for all environments.

Export Configuration

Export Formats

JSON:

wheels config env export production --format=json

YAML:

wheels config env export production --format=yaml

Environment variables:

wheels config env export production --format=env

Export Options

# Include descriptions
wheels config env export production --verbose

# Exclude sensitive data
wheels config env export production --safe

# Filter specific settings
wheels config env export production --filter=cache*

Import Configuration

From File

wheels config env import staging --from=staging-config.json

Merge Import

wheels config env import staging --from=updates.json --merge

Validation

wheels config env import staging --from=config.json --validate

Environment Templates

Create Template

wheels config env export production --template > environment-template.json

Use Template

wheels config env create qa --from-template=environment-template.json

Bulk Operations

Update Multiple Environments

# Update all non-production environments
for env in development staging qa; do
  wheels config env sync production $env --filter=cache*
done

Batch Validation

wheels config env validate all --report=validation-report.html

Configuration Inheritance

Show inheritance chain:

wheels config env show staging --show-inheritance

Output:

Inheritance Chain:
1. /config/staging/settings.cfm (environment-specific)
2. /config/settings.cfm (global)
3. Framework defaults

Override Summary:
- From global: 15 settings
- From defaults: 45 settings
- Environment-specific: 8 settings

Security Features

Safe Mode

Hide sensitive values:

wheels config env show production --safe

Sensitive patterns:

  • *password*

  • *secret*

  • *key*

  • *token*

Audit Trail

wheels config env audit production

Shows configuration change history.

Integration

CI/CD Usage

- name: Validate configs
  run: |
    wheels config env validate all
    wheels config env compare staging production --fail-on-diff

Documentation

# Generate environment docs
wheels config env export all --format=markdown > ENVIRONMENTS.md

Best Practices

  1. Regular Validation: Check configs before deployment

  2. Environment Parity: Keep environments similar

  3. Safe Exports: Never export sensitive data

  4. Version Control: Track configuration files

  5. Document Differences: Explain why environments differ

Troubleshooting

Validation Failures

  • Check syntax errors

  • Verify required settings

  • Test database connections

Sync Issues

  • Review protected settings

  • Check file permissions

  • Verify source environment

Import Problems

  • Validate import format

  • Check for conflicts

  • Review type mismatches

Notes

  • Some operations require application restart

  • Sensitive values protected by default

  • Changes logged for audit purposes

  • Use templates for consistency

See Also

wheels test run

Run TestBox tests for your application with advanced features.

Synopsis

wheels test run [spec] [options]

Description

The wheels test run command executes your application's TestBox test suite with support for watching, filtering, and various output formats. This is the primary command for running your application tests (as opposed to framework tests).

Arguments

Argument
Description
Default

spec

Specific test spec or directory

All tests

Options

Option
Description
Default

--watch

Watch for changes and rerun

false

--reporter

TestBox reporter

simple

--recurse

Recurse directories

true

--bundles

Test bundles to run

--labels

Filter by labels

--excludes

Patterns to exclude

--filter

Test name filter

--outputFile

Output results to file

--verbose

Verbose output

false

--help

Show help information

Examples

Run all tests

wheels test run

Run specific test file

wheels test run tests/models/UserTest.cfc

Run tests in directory

wheels test run tests/controllers/

Watch mode

wheels test run --watch

Run specific bundles

wheels test run --bundles=models,controllers

Filter by labels

wheels test run --labels=unit,critical

Use different reporter

wheels test run --reporter=json
wheels test run --reporter=junit
wheels test run --reporter=text

Save results to file

wheels test run --reporter=junit --outputFile=test-results.xml

Test Structure

Standard test directory layout:

/tests/
├── Application.cfc      # Test configuration
├── models/             # Model tests
│   ├── UserTest.cfc
│   └── ProductTest.cfc
├── controllers/        # Controller tests
│   ├── UsersTest.cfc
│   └── ProductsTest.cfc
├── views/             # View tests
├── integration/       # Integration tests
└── helpers/          # Test helpers

Writing Tests

Model Test Example

component extends="testbox.system.BaseSpec" {

    function run() {
        describe("User Model", function() {
            
            beforeEach(function() {
                // Reset test data
                application.wirebox.getInstance("User").deleteAll();
            });
            
            it("validates required fields", function() {
                var user = model("User").new();
                expect(user.valid()).toBeFalse();
                expect(user.errors).toHaveKey("email");
                expect(user.errors).toHaveKey("username");
            });
            
            it("saves with valid data", function() {
                var user = model("User").new(
                    email="test@example.com",
                    username="testuser",
                    password="secret123"
                );
                expect(user.save()).toBeTrue();
                expect(user.id).toBeGT(0);
            });
            
            it("prevents duplicate emails", function() {
                var user1 = model("User").create(
                    email="test@example.com",
                    username="user1"
                );
                
                var user2 = model("User").new(
                    email="test@example.com",
                    username="user2"
                );
                
                expect(user2.valid()).toBeFalse();
                expect(user2.errors.email).toContain("already exists");
            });
            
        });
    }

}

Controller Test Example

component extends="testbox.system.BaseSpec" {

    function run() {
        describe("Products Controller", function() {
            
            it("lists all products", function() {
                // Create test data
                var product = model("Product").create(name="Test Product");
                
                // Make request
                var event = execute(
                    event="products.index",
                    renderResults=true
                );
                
                // Assert response
                expect(event.getRenderedContent()).toInclude("Test Product");
                expect(event.getValue("products")).toBeArray();
            });
            
            it("requires auth for create", function() {
                var event = execute(
                    event="products.create",
                    renderResults=false
                );
                
                expect(event.getValue("relocate_URI")).toBe("/login");
            });
            
        });
    }

}

Test Configuration

/tests/Application.cfc

component {
    this.name = "WheelsTestingSuite" & Hash(GetCurrentTemplatePath());
    
    // Use test datasource
    this.datasources["wheelstestdb"] = {
        url = "jdbc:h2:mem:wheelstestdb;MODE=MySQL"
    };
    this.datasource = "wheelstestdb";
    
    // Test settings
    this.testbox = {
        testBundles = "tests",
        recurse = true,
        reporter = "simple",
        labels = "",
        options = {}
    };
}

Watch Mode

Watch mode reruns tests on file changes:

wheels test run --watch

Output:

[TestBox Watch] Monitoring for changes...
[TestBox Watch] Watching: /tests, /models, /controllers

[14:23:45] Change detected: models/User.cfc
[14:23:45] Running tests...

✓ User Model > validates required fields (12ms)
✓ User Model > saves with valid data (45ms)
✓ User Model > prevents duplicate emails (23ms)

Tests: 3 passed, 0 failed
Time: 80ms

[TestBox Watch] Waiting for changes...

Reporters

Simple (Default)

wheels test run --reporter=simple
  • Colored console output

  • Shows progress dots

  • Summary at end

Text

wheels test run --reporter=text
  • Plain text output

  • Good for CI systems

  • No colors

JSON

wheels test run --reporter=json
{
    "totalDuration": 523,
    "totalSpecs": 25,
    "totalPass": 24,
    "totalFail": 1,
    "totalError": 0,
    "totalSkipped": 0
}

JUnit

wheels test run --reporter=junit --outputFile=results.xml
  • JUnit XML format

  • For CI integration

  • Jenkins compatible

TAP

wheels test run --reporter=tap
  • Test Anything Protocol

  • Cross-language format

Filtering Tests

By Bundle

# Run only model tests
wheels test run --bundles=models

# Run multiple bundles
wheels test run --bundles=models,controllers

By Label

it("can authenticate", function() {
    // test code
}).labels("auth,critical");
# Run only critical tests
wheels test run --labels=critical

# Run auth OR api tests
wheels test run --labels=auth,api

By Name Filter

# Run tests matching pattern
wheels test run --filter="user"
wheels test run --filter="validate*"

Exclude Patterns

# Skip slow tests
wheels test run --excludes="*slow*,*integration*"

Parallel Execution

Run tests in parallel threads:

wheels test run --threads=4

Benefits:

  • Faster execution

  • Better CPU utilization

  • Finds concurrency issues

Code Coverage

Generate coverage reports:

wheels test run --coverage --coverageOutputDir=coverage/

View report:

open coverage/index.html

Test Helpers

Create reusable test utilities:

// /tests/helpers/TestHelper.cfc
component {
    
    function createTestUser(struct overrides={}) {
        var defaults = {
            email: "test#CreateUUID()#@example.com",
            username: "user#CreateUUID()#",
            password: "testpass123"
        };
        
        return model("User").create(
            argumentCollection = defaults.append(arguments.overrides)
        );
    }
    
    function loginAs(required user) {
        session.userId = arguments.user.id;
        session.isAuthenticated = true;
    }
    
}

Database Strategies

Transaction Rollback

function beforeAll() {
    transaction action="begin";
}

function afterAll() {
    transaction action="rollback";
}

Database Cleaner

function beforeEach() {
    queryExecute("DELETE FROM users");
    queryExecute("DELETE FROM products");
}

Fixtures

function loadFixtures() {
    var users = deserializeJSON(
        fileRead("/tests/fixtures/users.json")
    );
    
    for (var userData in users) {
        model("User").create(userData);
    }
}

CI/CD Integration

GitHub Actions

- name: Run tests
  run: |
    wheels test run --reporter=junit --outputFile=test-results.xml
    
- name: Upload results
  uses: actions/upload-artifact@v2
  with:
    name: test-results
    path: test-results.xml

Pre-commit Hook

#!/bin/bash
# .git/hooks/pre-commit

echo "Running tests..."
wheels test run --labels=unit

if [ $? -ne 0 ]; then
    echo "Tests failed. Commit aborted."
    exit 1
fi

Performance Tips

  1. Use labels for fast feedback

    wheels test run --labels=unit  # Fast
    wheels test run --labels=integration  # Slow
  2. Parallel execution

    wheels test run --threads=4
  3. Watch specific directories

    wheels test run tests/models --watch
  4. Skip slow tests during development

    wheels test run --excludes="*integration*"

Common Issues

Out of Memory

# Increase memory
box server set jvm.heapSize=1024
box server restart

Test Pollution

  • Use beforeEach/afterEach

  • Reset global state

  • Use transactions

Flaky Tests

  • Avoid time-dependent tests

  • Mock external services

  • Use fixed test data

See Also

wheels env

Base command for environment management in Wheels applications.

Synopsis

wheels env [subcommand] [options]

Description

The wheels env command provides comprehensive environment management for Wheels applications. It handles environment configuration, switching between environments, and managing environment-specific settings.

Subcommands

Command
Description

setup

Setup a new environment

list

List available environments

switch

Switch to a different environment

Options

Option
Description

--help

Show help information

--version

Show version information

Direct Usage

When called without subcommands, displays current environment:

wheels env

Output:

Current Environment: development
Configuration File: /config/development/settings.cfm
Database: wheels_dev
Mode: development
Debug: enabled

Examples

Show current environment

wheels env

Quick environment info

wheels env --info

List all environments

wheels env list

Switch environment

wheels env switch production

Environment Configuration

Each environment has its own configuration:

/config/
  ├── development/
  │   └── settings.cfm
  ├── testing/
  │   └── settings.cfm
  ├── production/
  │   └── settings.cfm
  └── environment.cfm

Environment Variables

The command respects these environment variables:

Variable
Description
Default

WHEELS_ENV

Current environment

development

WHEELS_DATASOURCE

Database name

Per environment

WHEELS_DEBUG

Debug mode

Per environment

Environment Detection

Order of precedence:

  1. Command line argument

  2. WHEELS_ENV environment variable

  3. .wheels-env file

  4. Default (development)

Common Environments

Development

  • Debug enabled

  • Detailed error messages

  • Hot reload active

  • Development database

Testing

  • Test database

  • Fixtures loaded

  • Debug enabled

  • Isolated from production

Production

  • Debug disabled

  • Optimized performance

  • Production database

  • Error handling active

Staging

  • Production-like

  • Separate database

  • Debug configurable

  • Pre-production testing

Environment Files

.wheels-env

Local environment override:

production

.env.[environment]

Environment-specific variables:

# .env.production
DATABASE_URL=mysql://prod@host/db
CACHE_ENABLED=true
DEBUG_MODE=false

Integration

With Other Commands

Many commands respect current environment:

# Uses current environment's database
wheels dbmigrate latest

# Reloads in current environment
wheels reload

# Tests run in test environment
wheels test run

In Application Code

Access current environment:

<cfset currentEnv = get("environment")>
<cfif currentEnv eq "production">
    <!--- Production-specific code --->
</cfif>

Best Practices

  1. Never commit .wheels-env file

  2. Use testing environment for tests

  3. Match staging to production closely

  4. Separate databases per environment

  5. Environment-specific configuration files

Use Cases

  1. Local Development: Switch between feature environments

  2. Testing: Isolated test environment

  3. Deployment: Environment-specific configurations

  4. Debugging: Quick environment switching

  5. Team Development: Consistent environments

Notes

  • Environment changes may require application restart

  • Database connections are environment-specific

  • Some settings only take effect after reload

  • Use version control for environment configs

See Also

wheels analyze

Base command for code analysis and quality checks.

Synopsis

Description

The wheels analyze command provides comprehensive code analysis tools for Wheels applications. It helps identify code quality issues, performance bottlenecks, security vulnerabilities, and provides actionable insights for improvement.

Subcommands

Options

Direct Usage

When called without subcommands, runs all analyses:

This executes:

  1. Code quality analysis

  2. Performance analysis

  3. Security scanning (if not deprecated)

Examples

Run all analyses

Quick analysis with summary

Analyze specific directory

Generate analysis report

Analysis Overview

The analyze command examines:

Code Quality

  • Coding standards compliance

  • Code complexity metrics

  • Duplication detection

  • Best practices adherence

Performance

  • N+1 query detection

  • Slow query identification

  • Memory usage patterns

  • Cache effectiveness

Security

  • SQL injection risks

  • XSS vulnerabilities

  • Insecure configurations

  • Outdated dependencies

Output Example

Analysis Configuration

Configure via .wheels-analysis.json:

Integration with CI/CD

GitHub Actions Example

Quality Gates

Set minimum scores:

Report Formats

HTML Report

  • Interactive dashboard

  • Detailed issue breakdown

  • Code snippets with issues

JSON Report

  • Machine-readable format

  • CI/CD integration

  • Custom processing

Markdown Report

  • Documentation-friendly

  • Pull request comments

  • Wiki integration

Analysis Rules

Built-in Rules

  • CFScript best practices

  • SQL query optimization

  • Security patterns

  • Memory management

Custom Rules

Create custom rules in .wheels-analysis-rules/:

Baseline

Track improvement over time:

Ignoring Issues

Inline Comments

Configuration File

Performance Tips

  1. Incremental Analysis: Analyze only changed files

  2. Parallel Processing: Use multiple cores

  3. Cache Results: Reuse analysis for unchanged files

  4. Focused Scans: Target specific directories

Use Cases

  1. Pre-commit Hooks: Catch issues before commit

  2. Pull Request Checks: Automated code review

  3. Technical Debt: Track and reduce over time

  4. Team Standards: Enforce coding guidelines

  5. Performance Monitoring: Identify bottlenecks

Best Practices

  1. Run analysis regularly

  2. Fix high-priority issues first

  3. Set realistic quality gates

  4. Track metrics over time

  5. Integrate with development workflow

Troubleshooting

Analysis Takes Too Long

  • Exclude vendor directories

  • Use incremental mode

  • Increase memory allocation

Too Many False Positives

  • Tune rule sensitivity

  • Add specific ignores

  • Update rule definitions

Notes

  • First run may take longer due to initial scanning

  • Results are cached for performance

  • Some rules require database connection

  • Memory usage scales with codebase size

See Also

wheels plugins install

Installs a CFWheels plugin from various sources including ForgeBox, GitHub, or local files.

Usage

Parameters

  • plugin - (Required) Plugin name, ForgeBox slug, GitHub URL, or local path

  • --version - (Optional) Specific version to install. Default: latest

  • --force - (Optional) Force installation even if plugin exists

  • --save - (Optional) Save plugin to box.json dependencies

Description

The plugins install command downloads and installs CFWheels plugins into your application. It supports multiple installation sources:

  • ForgeBox Registry: Official and community plugins

  • GitHub Repositories: Direct installation from GitHub

  • Local Files: ZIP files or directories

  • URL Downloads: Direct ZIP file URLs

The command automatically:

  • Checks plugin compatibility

  • Resolves dependencies

  • Backs up existing plugins

  • Runs installation scripts

Examples

Install from ForgeBox

Install specific version

Install from GitHub

Install from local file

Force reinstall

Install and save to dependencies

Installation Process

  1. Download: Fetches plugin from specified source

  2. Validation: Checks compatibility and requirements

  3. Backup: Creates backup of existing plugin (if any)

  4. Installation: Extracts files to plugins directory

  5. Dependencies: Installs required dependencies

  6. Initialization: Runs plugin setup scripts

  7. Verification: Confirms successful installation

Output

Plugin Sources

ForgeBox

GitHub

Direct URL

Notes

  • Plugins must be compatible with your CFWheels version

  • Always backup your application before installing plugins

  • Some plugins require manual configuration after installation

  • Use wheels plugins list to verify installation

  • Restart your application to activate new plugins

wheels plugins list

Lists all installed plugins in your CFWheels application with version and status information.

Usage

Parameters

  • --format - (Optional) Output format: table, json, simple. Default: table

  • --status - (Optional) Filter by status: all, active, inactive. Default: all

Description

The plugins list command displays information about all plugins installed in your CFWheels application, including:

  • Plugin name and version

  • Installation status (active/inactive)

  • Compatibility with current CFWheels version

  • Description and author information

  • Dependencies on other plugins

Examples

List all plugins

Show only active plugins

Export as JSON

Simple listing (names only)

Output

Table Format (Default)

JSON Format

Plugin Statuses

  • Active: Plugin is loaded and functioning

  • Inactive: Plugin is installed but not loaded

  • Error: Plugin failed to load (check logs)

  • Incompatible: Plugin requires different CFWheels version

Notes

  • Plugins are loaded from the /plugins directory

  • Plugin order matters for dependencies

  • Incompatible plugins may cause application errors

  • Use wheels plugins install to add new plugins

wheels plugins

Base command for plugin management in Wheels applications.

Synopsis

Description

The wheels plugins command provides comprehensive plugin management for Wheels applications. It handles plugin discovery, installation, configuration, and lifecycle management.

Subcommands

Options

Direct Usage

When called without subcommands, displays plugin overview:

Output:

Examples

Show plugin overview

Quick plugin check

Update all plugins

Plugin system info

Plugin System

Plugin Structure

Plugin Metadata

Each plugin contains plugin.json:

Plugin Registry

Official Registry

Default source for plugins:

Custom Registries

Configure additional sources:

Plugin Lifecycle

Discovery

Installation

Configuration

Updates

Plugin Development

Create Plugin

Plugin API

Environment Support

Environment-Specific Plugins

Conditional Loading

Plugin Commands

Plugins can register custom commands:

Usage:

Dependency Management

Automatic Resolution

Conflict Resolution

Options:

  • prompt: Ask for each conflict

  • newest: Use newest version

  • oldest: Keep existing version

Plugin Storage

Global Plugins

Shared across projects:

Location: ~/.wheels/plugins/

Project Plugins

Project-specific:

Location: /plugins/

Security

Plugin Verification

Permission Control

Troubleshooting

Common Issues

  1. Plugin Not Loading

  2. Dependency Conflicts

  3. Version Incompatibility

Best Practices

  1. Version Lock: Lock plugin versions for production

  2. Test Updates: Test in development first

  3. Backup: Backup before major updates

  4. Documentation: Document custom plugins

  5. Security: Verify plugin sources

Plugin Cache

Clear Cache

Rebuild Cache

Notes

  • Plugins are loaded in dependency order

  • Some plugins require application restart

  • Global plugins override project plugins

  • Plugin conflicts are resolved by load order

See Also

wheels plugins remove

Removes an installed plugin from your CFWheels application.

Usage

Parameters

  • plugin - (Required) Name of the plugin to remove

  • --backup - (Optional) Create a backup before removal. Default: true

  • --force - (Optional) Force removal even if other plugins depend on it

Description

The plugins remove command safely uninstalls a plugin from your CFWheels application. It:

  • Checks for dependent plugins

  • Creates a backup (by default)

  • Removes plugin files

  • Cleans up configuration

  • Updates plugin registry

Examples

Basic plugin removal

Remove without backup

Force removal (ignore dependencies)

Remove multiple plugins

Removal Process

  1. Dependency Check: Ensures no other plugins depend on this one

  2. Backup Creation: Saves plugin files to backup directory

  3. Deactivation: Disables plugin in application

  4. File Removal: Deletes plugin files and directories

  5. Cleanup: Removes configuration entries

  6. Verification: Confirms successful removal

Output

Dependency Handling

If other plugins depend on the one being removed:

Backup Management

Backups are stored in /backups/plugins/ with timestamp:

  • Format: [plugin-name]-[version]-[timestamp].zip

  • Example: authentication-2.1.0-20240115143022.zip

Restore from backup

Notes

  • Always restart your application after removing plugins

  • Backups are kept for 30 days by default

  • Some plugins may leave configuration files that need manual cleanup

  • Database tables created by plugins are not automatically removed

  • Use wheels plugins list to verify removal

wheels env switch

Switch to a different environment in your Wheels application.

Synopsis

Description

The wheels env switch command changes the active environment for your Wheels application. It updates configuration files, environment variables, and optionally restarts services to apply the new environment settings.

Arguments

Options

Examples

Switch to staging

Switch with application restart

Force switch without validation

Switch with backup

Quiet switch for scripts

What It Does

  1. Validates Target Environment:

    • Checks if environment exists

    • Verifies configuration

    • Tests database connection

  2. Updates Configuration:

    • Sets WHEELS_ENV variable

    • Updates .wheels-env file

    • Modifies environment.cfm if needed

  3. Applies Changes:

    • Clears caches

    • Reloads configuration

    • Restarts services (if requested)

  4. Verifies Switch:

    • Confirms environment active

    • Checks application health

    • Reports status

Output Example

Environment File Updates

.wheels-env

Before:

After:

Environment Variables

Updates system environment:

Validation Process

Before switching, validates:

  1. Configuration:

    • File exists

    • Syntax valid

    • Required settings present

  2. Database:

    • Connection works

    • Tables accessible

    • Migrations current

  3. Dependencies:

    • Required services available

    • File permissions correct

    • Resources accessible

Switch Strategies

Safe Switch (Default)

  • Full validation

  • Graceful transition

  • Rollback on error

Fast Switch

  • Skip validation

  • Immediate switch

  • Use with caution

Zero-Downtime Switch

  • Prepare new environment

  • Switch load balancer

  • No service interruption

Backup and Restore

Create Backup

Restore from Backup

Manual Restore

Service Management

With Restart

Restarts:

  • Application server

  • Cache services

  • Background workers

Service-Specific

Pre/Post Hooks

Configure in .wheels-cli.json:

Environment-Specific Actions

Development → Production

Production → Development

Integration

CI/CD Pipeline

Deployment Scripts

Rollback

If switch fails or causes issues:

Troubleshooting

Switch Failed

  • Check validation errors

  • Verify target environment exists

  • Use --force if necessary

Application Not Responding

  • Check service status

  • Review error logs

  • Manually restart services

Database Connection Issues

  • Verify credentials

  • Check network access

  • Test connection manually

Best Practices

  1. Always Validate: Don't skip checks in production

  2. Use Backups: Enable backup for critical switches

  3. Test First: Switch in staging before production

  4. Monitor After: Check application health post-switch

  5. Document Changes: Log environment switches

Security Considerations

  • Production switches require confirmation

  • Sensitive configs protected

  • Audit trail maintained

  • Access controls enforced

Notes

  • Some changes require application restart

  • Database connections may need reset

  • Cached data cleared on switch

  • Background jobs may need restart

See Also

- Run tests

- Run specific tests

- Debug test execution

- Run TestBox application tests

- Generate coverage reports

- Debug test execution

- Run all migrations

- View migration status

- Seed database with data

- Generate models from schema

- Run migrations before seeding

- Export/import database structure

- Generate models for seeding

- Test with seeded data

- Run tests normally

- Run specific tests

- Coverage analysis

- Environment management overview

- List environments

- Switch environments

- Configuration management

- List configuration

- Environment config

- Environment management

- Set configuration values

- Environment configuration

- Environment management

- Environment management overview

- Setup new environment

- Switch environments

- List configuration

- List all settings

- Set configuration values

- Environment management

- Run framework tests

- Generate coverage

- Debug tests

- Generate test files

- Setup new environment

- List environments

- Switch environments

- Configuration management

Command
Description
Option
Description

- Code quality analysis

- Performance analysis

- Security scanning

- Run tests

Command
Description
Option
Description

- List plugins

- Install plugins

- Remove plugins

Argument
Description
Default
Option
Description
Default

- Environment management overview

- List environments

- Setup environments

- Reload application

wheels test
wheels test run
wheels test debug
Testing Best Practices
wheels test run
wheels test coverage
wheels test debug
wheels dbmigrate latest
wheels dbmigrate info
wheels db seed
wheels generate model
wheels dbmigrate latest
wheels db schema
wheels generate model
wheels test run
wheels test
wheels test run
wheels test coverage
Debugging Guide
wheels env
wheels env list
wheels env switch
wheels config
wheels config list
wheels config env
wheels env
Configuration Guide
wheels config set
wheels config env
wheels env
Configuration Guide
wheels env
wheels env setup
wheels env switch
wheels config list
wheels config list
wheels config set
wheels env
Configuration Guide
wheels test
wheels test coverage
wheels test debug
wheels generate test
wheels env setup
wheels env list
wheels env switch
wheels config
wheels analyze [subcommand] [options]

code

Analyze code quality and patterns

performance

Analyze performance characteristics

security

Security vulnerability analysis (deprecated)

--help

Show help information

--version

Show version information

wheels analyze
wheels analyze
wheels analyze --summary
wheels analyze --path=./models
wheels analyze --report=html --output=./analysis-report
Wheels Code Analysis Report
==========================

Code Quality
------------
✓ Files analyzed: 234
✓ Total lines: 12,456
⚠ Issues found: 23
  - High priority: 3
  - Medium priority: 12
  - Low priority: 8

Performance
-----------
✓ Queries analyzed: 156
⚠ Potential N+1 queries: 4
⚠ Slow queries detected: 2
✓ Cache hit ratio: 87%

Security (Deprecated)
--------------------
! Security analysis has been deprecated
! Use 'wheels security scan' instead

Summary Score: B+ (82/100)
{
  "analyze": {
    "exclude": [
      "vendor/**",
      "tests/**",
      "*.min.js"
    ],
    "rules": {
      "complexity": {
        "maxComplexity": 10,
        "maxDepth": 4
      },
      "duplication": {
        "minLines": 5,
        "threshold": 0.05
      }
    },
    "performance": {
      "slowQueryThreshold": 1000,
      "cacheTargetRatio": 0.8
    }
  }
}
- name: Run code analysis
  run: |
    wheels analyze --format=json --output=analysis.json
    wheels analyze --format=badge > analysis-badge.svg
wheels analyze --min-score=80 --fail-on-issues=high
wheels analyze --report=html
wheels analyze --format=json
wheels analyze --format=markdown
module.exports = {
  name: "custom-rule",
  check: function(file, content) {
    // Rule implementation
  }
};
# Create baseline
wheels analyze --save-baseline

# Compare with baseline
wheels analyze --compare-baseline
// wheels-analyze-ignore-next-line
complexQuery = ormExecuteQuery(sql, params);

/* wheels-analyze-ignore-start */
// Complex code block
/* wheels-analyze-ignore-end */
{
  "ignore": [
    {
      "rule": "sql-injection",
      "file": "legacy/*.cfc"
    }
  ]
}
wheels plugins install <plugin> [--version=<version>] [--force] [--save]
wheels plugins install authentication
wheels plugins install dbmigrate --version=3.0.0
wheels plugins install https://github.com/user/wheels-plugin
wheels plugins install /path/to/plugin.zip
wheels plugins install routing --force
wheels plugins install cache-manager --save
Installing plugin: authentication
==================================

Downloading from ForgeBox... ✓
Checking compatibility... ✓
Creating backup... ✓
Installing plugin files... ✓
Installing dependencies... ✓
Running setup scripts... ✓

Plugin 'authentication' (v2.1.0) installed successfully!

Post-installation notes:
- Run 'wheels reload' to activate the plugin
- Check documentation at /plugins/authentication/README.md
- Configure settings in /config/authentication.cfm
# Install by name (searches ForgeBox)
wheels plugins install plugin-name

# Install specific ForgeBox ID
wheels plugins install forgebox:plugin-slug
# HTTPS URL
wheels plugins install https://github.com/user/repo

# GitHub shorthand
wheels plugins install github:user/repo

# Specific branch/tag
wheels plugins install github:user/repo#v2.0.0
wheels plugins install https://example.com/plugin.zip
wheels plugins list [--format=<format>] [--status=<status>]
wheels plugins list
wheels plugins list --status=active
wheels plugins list --format=json
wheels plugins list --format=simple
CFWheels Plugins
================

Name                Version    Status    Compatible    Description
-----------------------------------------------------------------
Authentication      2.1.0      Active    ✓            User authentication and authorization
DBMigrate          3.0.2      Active    ✓            Database migration management
Routing            1.5.1      Active    ✓            Advanced routing capabilities
TestBox            2.0.0      Inactive  ✓            Enhanced testing framework
CacheManager       1.2.3      Active    ✗            Advanced caching (requires update)

Total: 5 plugins (4 active, 1 inactive)
{
  "plugins": [
    {
      "name": "Authentication",
      "version": "2.1.0",
      "status": "active",
      "compatible": true,
      "description": "User authentication and authorization",
      "author": "CFWheels Team",
      "dependencies": []
    }
  ],
  "summary": {
    "total": 5,
    "active": 4,
    "inactive": 1
  }
}
wheels plugins [subcommand] [options]

list

List installed plugins

install

Install a plugin

remove

Remove a plugin

--help

Show help information

--version

Show version information

wheels plugins
Wheels Plugin Manager
====================

Installed Plugins: 5
├── authentication (v2.1.0) - User authentication system
├── pagination (v1.5.2) - Advanced pagination helpers
├── validation (v3.0.1) - Extended validation rules
├── caching (v2.2.0) - Enhanced caching strategies
└── api-tools (v1.8.3) - RESTful API utilities

Available Updates: 2
- validation: v3.0.1 → v3.1.0
- api-tools: v1.8.3 → v2.0.0

Run 'wheels plugins list' for detailed information
wheels plugins
wheels plugins --check
wheels plugins --update-all
wheels plugins --info
/plugins/
├── authentication/
│   ├── Authentication.cfc
│   ├── config/
│   ├── models/
│   ├── views/
│   └── plugin.json
├── pagination/
└── ...
{
  "name": "authentication",
  "version": "2.1.0",
  "description": "User authentication system",
  "author": "Wheels Community",
  "wheels": ">=2.0.0",
  "dependencies": {
    "validation": ">=3.0.0"
  }
}
https://plugins.cfwheels.org/
{
  "pluginRegistries": [
    "https://plugins.cfwheels.org/",
    "https://company.com/wheels-plugins/"
  ]
}
# Search for plugins
wheels plugins search authentication

# Browse categories
wheels plugins browse --category=security
# Install from registry
wheels plugins install authentication

# Install from GitHub
wheels plugins install github:user/wheels-plugin

# Install from file
wheels plugins install ./my-plugin.zip
# Configure plugin
wheels plugins configure authentication

# View configuration
wheels plugins config authentication
# Check for updates
wheels plugins outdated

# Update specific plugin
wheels plugins update authentication

# Update all plugins
wheels plugins update --all
# Generate plugin scaffold
wheels generate plugin my-plugin

# Plugin structure created:
# /plugins/my-plugin/
#   ├── MyPlugin.cfc
#   ├── plugin.json
#   ├── config/
#   ├── tests/
#   └── README.md
component extends="wheels.Plugin" {
    
    function init() {
        this.version = "1.0.0";
        this.author = "Your Name";
        this.description = "Plugin description";
    }
    
    function setup() {
        // Plugin initialization
    }
    
    function teardown() {
        // Plugin cleanup
    }
}
{
  "plugins": {
    "production": ["caching", "monitoring"],
    "development": ["debug-toolbar", "profiler"],
    "all": ["authentication", "validation"]
  }
}
// In environment.cfm
if (get("environment") == "development") {
    addPlugin("debug-toolbar");
}
// In plugin
this.commands = {
    "auth:create-user": "commands/CreateUser.cfc",
    "auth:reset-password": "commands/ResetPassword.cfc"
};
wheels auth:create-user admin@example.com
wheels auth:reset-password user123
# Installs plugin and dependencies
wheels plugins install api-tools
# Also installs: validation, serialization
# When conflicts exist
wheels plugins install authentication --resolve=prompt
wheels plugins install authentication --global
wheels plugins install authentication
# Verify plugin signatures
wheels plugins verify authentication

# Install only verified plugins
wheels plugins install authentication --verified-only
{
  "pluginPermissions": {
    "fileSystem": ["read", "write"],
    "network": ["http", "https"],
    "database": ["read", "write"]
  }
}
wheels plugins diagnose authentication
wheels plugins deps --tree
wheels plugins check-compatibility
wheels plugins cache clear
wheels plugins cache rebuild
wheels plugins remove <plugin> [--backup] [--force]
wheels plugins remove authentication
wheels plugins remove cache-manager --no-backup
wheels plugins remove routing --force
wheels plugins remove plugin1
wheels plugins remove plugin2
Removing plugin: authentication
================================

Checking dependencies... ✓
Creating backup at /backups/plugins/authentication-2.1.0-20240115.zip... ✓
Deactivating plugin... ✓
Removing plugin files... ✓
Cleaning configuration... ✓

Plugin 'authentication' removed successfully!

Note: Backup saved to /backups/plugins/authentication-2.1.0-20240115.zip
      You may need to restart your application.
Cannot remove plugin: routing
=============================

The following plugins depend on 'routing':
- advanced-routing (v1.2.0)
- api-framework (v3.0.1)

Options:
1. Remove dependent plugins first
2. Use --force to remove anyway (may break functionality)
# Manually restore a plugin
wheels plugins install /backups/plugins/authentication-2.1.0-20240115.zip
wheels env switch [name] [options]

name

Target environment name

Required

--check

Validate before switching

true

--restart

Restart application after switch

false

--backup

Backup current environment

false

--force

Force switch even with issues

false

--quiet

Suppress output

false

--help

Show help information

wheels env switch staging
wheels env switch production --restart
wheels env switch testing --force
wheels env switch production --backup
wheels env switch development --quiet
Switching environment...

Current: development
Target:  staging

✓ Validating staging environment
✓ Configuration valid
✓ Database connection successful
✓ Updating environment settings
✓ Clearing caches
✓ Environment switched successfully

New Environment: staging
Database: wheels_staging
Debug: Enabled
Cache: Partial
development
staging
export WHEELS_ENV=staging
export WHEELS_DATASOURCE=wheels_staging
wheels env switch production
wheels env switch staging --force --no-check
wheels env switch production --strategy=blue-green
wheels env switch production --backup
# Creates: .wheels-env-backup-20240115-103045
wheels env restore --from=.wheels-env-backup-20240115-103045
# If switch fails
cp .wheels-env-backup-20240115-103045 .wheels-env
wheels reload
wheels env switch production --restart
wheels env switch staging --restart-services=app,cache
{
  "env": {
    "switch": {
      "pre": [
        "wheels test run --quick",
        "git stash"
      ],
      "post": [
        "wheels dbmigrate latest",
        "wheels cache clear",
        "npm run build"
      ]
    }
  }
}
wheels env switch production
# Warning: Switching from development to production
# - Debug will be disabled
# - Caching will be enabled
# - Error details will be hidden
# Continue? (y/N)
wheels env switch development
# Warning: Switching from production to development
# - Debug will be enabled
# - Caching will be disabled
# - Sensitive data may be exposed
# Continue? (y/N)
- name: Switch to staging
  run: |
    wheels env switch staging --check
    wheels test run
    wheels deploy exec staging
#!/bin/bash
# deploy.sh

# Switch environment
wheels env switch $1 --backup

# Run migrations
wheels dbmigrate latest

# Clear caches
wheels cache clear

# Verify
wheels env | grep $1
# Automatic rollback
wheels env switch production --auto-rollback

# Manual rollback
wheels env switch:rollback

# Force previous environment
wheels env switch development --force

wheels analyze performance

Analyzes application performance, identifying bottlenecks and optimization opportunities in your CFWheels application.

Usage

wheels analyze performance [--profile=<duration>] [--threshold=<ms>] [--format=<format>]

Parameters

  • --profile - (Optional) Duration to profile the application (e.g., 30s, 5m). Default: 60s

  • --threshold - (Optional) Minimum execution time in milliseconds to report. Default: 100

  • --format - (Optional) Output format: console, json, csv. Default: console

Description

The analyze performance command profiles your CFWheels application to identify performance bottlenecks and provide optimization recommendations. It monitors:

  • Request execution times

  • Database query performance

  • Memory usage patterns

  • Cache effectiveness

  • View rendering times

  • Component instantiation overhead

Examples

Basic performance analysis

wheels analyze performance

Profile for 5 minutes

wheels analyze performance --profile=5m

Show only slow operations (>500ms)

wheels analyze performance --threshold=500

Export results as CSV

wheels analyze performance --format=csv > performance-report.csv

Output

The analysis provides:

  • Slowest Requests: Top 10 slowest request paths

  • Query Analysis: Slow queries and N+1 query detection

  • Memory Hotspots: Areas of high memory allocation

  • Cache Statistics: Hit/miss ratios for various caches

  • Recommendations: Specific optimization suggestions

Sample Output

Performance Analysis Results
===========================

Slowest Requests:
1. GET /users/search (avg: 850ms, calls: 45)
2. POST /orders/create (avg: 650ms, calls: 12)
3. GET /reports/generate (avg: 1200ms, calls: 8)

Database Issues:
- N+1 queries detected in UsersController.index
- Slow query in Order.findRecent() - 450ms avg
- Missing index suggested for users.created_at

Memory Usage:
- High allocation in ReportService.generate()
- Potential memory leak in SessionManager

Recommendations:
1. Add eager loading to UsersController.index
2. Create index on users.created_at
3. Implement query result caching for Order.findRecent()

Notes

  • Profiling adds minimal overhead to your application

  • Best run in a staging environment with production-like data

  • Can be integrated with APM tools for continuous monitoring

  • Results are aggregated across all application instances

wheels analyze code

Analyzes code quality in your CFWheels application, checking for best practices, potential issues, and code standards compliance.

Usage

wheels analyze code [path] [--type=<type>] [--format=<format>] [--output=<file>]

Parameters

  • path - (Optional) Specific file or directory to analyze. Defaults to entire application.

  • --type - (Optional) Type of analysis: all, complexity, standards, duplication. Default: all

  • --format - (Optional) Output format: console, json, html. Default: console

  • --output - (Optional) File path to save analysis results

Description

The analyze code command performs comprehensive code quality analysis on your CFWheels application. It checks for:

  • Code complexity and maintainability

  • Adherence to CFWheels coding standards

  • Potential bugs and code smells

  • Duplicate code detection

  • Function length and complexity metrics

  • Variable naming conventions

  • Deprecated function usage

Examples

Basic code analysis

wheels analyze code

Analyze specific directory

wheels analyze code app/controllers

Check only for code duplication

wheels analyze code --type=duplication

Generate HTML report

wheels analyze code --format=html --output=reports/code-analysis.html

Analyze complexity for models

wheels analyze code app/models --type=complexity

Output

The command provides detailed feedback including:

  • Complexity Score: Cyclomatic complexity for functions

  • Code Standards: Violations of CFWheels conventions

  • Duplicate Code: Similar code blocks that could be refactored

  • Suggestions: Recommendations for improvement

  • Metrics Summary: Overall code health indicators

Notes

  • Large codebases may take several minutes to analyze

  • The complexity threshold can be configured in settings

  • HTML reports include interactive charts and detailed breakdowns

  • Integration with CI/CD pipelines is supported via JSON output

wheels security scan

Scans your CFWheels application for security vulnerabilities and provides remediation recommendations.

Usage

wheels security scan [--level=<level>] [--fix] [--report=<file>] [--exclude=<patterns>]

Parameters

  • --level - (Optional) Scan level: basic, standard, comprehensive. Default: standard

  • --fix - (Optional) Automatically fix safe issues

  • --report - (Optional) Save detailed report to file

  • --exclude - (Optional) Comma-separated patterns to exclude from scan

Description

The security scan command performs comprehensive security analysis of your CFWheels application, checking for:

  • SQL injection vulnerabilities

  • Cross-site scripting (XSS) risks

  • Cross-site request forgery (CSRF) issues

  • Insecure direct object references

  • Security misconfigurations

  • Outdated dependencies with known vulnerabilities

  • Weak authentication patterns

  • Information disclosure risks

Examples

Standard security scan

wheels security scan

Comprehensive scan with auto-fix

wheels security scan --level=comprehensive --fix

Generate security report

wheels security scan --report=security-audit.html

Exclude test files

wheels security scan --exclude="tests/*,temp/*"

Quick basic scan

wheels security scan --level=basic

Scan Levels

Basic

  • Configuration checks

  • Known vulnerability patterns

  • Dependency scanning

Standard (Default)

  • All basic checks

  • Code analysis for common vulnerabilities

  • Authentication/authorization review

  • Input validation checks

Comprehensive

  • All standard checks

  • Deep code flow analysis

  • Third-party integration security

  • Performance impact analysis

  • Custom vulnerability rules

Output

Security Scan Results
====================

Scanning application...
✓ Configuration files
✓ Controllers (15 files)
✓ Models (8 files)
✓ Views (23 files)
✗ Dependencies (2 issues)

CRITICAL: 1 issue found
-----------------------
1. SQL Injection Risk
   File: /app/models/User.cfc
   Line: 45
   Code: findOne(where="id = #params.id#")
   Fix: Use parameterized queries
   
HIGH: 3 issues found
--------------------
1. XSS Vulnerability
   File: /app/views/users/show.cfm
   Line: 12
   Code: <h1>#user.name#</h1>
   Fix: Use htmlEditFormat() or encodeForHTML()

2. Missing CSRF Token
   File: /app/views/users/edit.cfm
   Line: 8
   Fix: Add authenticityToken() to form

3. Outdated Dependency
   Package: jackson-databind
   Version: 2.9.0 (CVE-2019-12345)
   Fix: Update to version 2.14.0 or higher

MEDIUM: 5 issues found
LOW: 12 issues found

Summary:
- Critical: 1
- High: 3
- Medium: 5
- Low: 12
- Total: 21 vulnerabilities

Recommended Actions:
1. Fix all CRITICAL issues immediately
2. Address HIGH issues before deployment
3. Plan remediation for MEDIUM issues
4. Review LOW issues for false positives

Auto-Fix Feature

The --fix flag automatically resolves safe issues:

wheels security scan --fix

Auto-fixing security issues...
✓ Added htmlEditFormat() to 3 output statements
✓ Added CSRF tokens to 2 forms
✓ Updated .htaccess security headers
✗ Cannot auto-fix: SQL injection (requires manual review)

Fixed 5 of 8 fixable issues
Manual intervention required for 3 issues

Report Formats

HTML Report

wheels security scan --report=security-report.html

Generates interactive HTML report with:

  • Executive summary

  • Detailed findings with code snippets

  • Remediation steps

  • Compliance mapping (OWASP, CWE)

JSON Report

wheels security scan --report=security-report.json

Machine-readable format for CI/CD integration

Integration

CI/CD Pipeline

# Example GitHub Actions
- name: Security Scan
  run: |
    wheels security scan --level=standard --report=scan.json
    if [ $? -ne 0 ]; then
      echo "Security vulnerabilities found"
      exit 1
    fi

Pre-commit Hook

#!/bin/bash
wheels security scan --level=basic
if [ $? -ne 0 ]; then
  echo "Commit blocked: Security issues detected"
  exit 1
fi

Notes

  • Scans are performed locally; no code is sent externally

  • False positives can be suppressed with inline comments

  • Regular scanning is recommended as part of development workflow

  • Keep scan rules updated with wheels deps update

  • Some fixes require manual review to ensure functionality

wheels optimize performance

Automatically applies performance optimizations to your CFWheels application based on analysis results.

Usage

wheels optimize performance [--target=<area>] [--aggressive] [--backup] [--dry-run]

Parameters

  • --target - (Optional) Specific area to optimize: all, database, caching, assets, code. Default: all

  • --aggressive - (Optional) Apply aggressive optimizations that may change behavior

  • --backup - (Optional) Create backup before applying changes. Default: true

  • --dry-run - (Optional) Show what would be changed without applying

Description

The optimize performance command automatically implements performance improvements identified through analysis. It can:

  • Add database indexes

  • Implement query result caching

  • Optimize asset delivery

  • Enable application-level caching

  • Refactor inefficient code patterns

  • Configure performance settings

Examples

Basic optimization

wheels optimize performance

Optimize database only

wheels optimize performance --target=database

Preview changes

wheels optimize performance --dry-run

Aggressive optimization

wheels optimize performance --aggressive

Skip backup

wheels optimize performance --no-backup

Optimization Targets

Database

  • Creates missing indexes

  • Optimizes slow queries

  • Adds query hints

  • Implements connection pooling

  • Configures query caching

Caching

  • Enables view caching

  • Implements action caching

  • Configures cache headers

  • Sets up CDN integration

  • Optimizes cache keys

Assets

  • Minifies CSS/JavaScript

  • Implements asset fingerprinting

  • Configures compression

  • Optimizes images

  • Sets up asset pipeline

Code

  • Refactors N+1 queries

  • Implements lazy loading

  • Optimizes loops

  • Reduces object instantiation

  • Improves algorithm efficiency

Output

Performance Optimization
========================

Analyzing application... ✓
Creating backup... ✓

Applying optimizations:

Database Optimizations:
✓ Added index on users.email
✓ Added composite index on orders(user_id, created_at)
✓ Implemented query caching for User.findActive()
✓ Optimized ORDER BY clause in Product.search()

Caching Optimizations:
✓ Enabled action caching for ProductsController.index
✓ Added fragment caching to product listings
✓ Configured Redis for session storage
✓ Set cache expiration for static content

Asset Optimizations:
✓ Minified 15 JavaScript files (saved 145KB)
✓ Compressed 8 CSS files (saved 62KB)
✓ Enabled gzip compression
✓ Configured browser caching headers

Code Optimizations:
✓ Fixed N+1 query in OrdersController.index
✓ Replaced 3 array loops with cfloop query
✓ Implemented lazy loading for User.orders
~ Skipped: Complex refactoring in ReportService (requires --aggressive)

Summary:
- Applied: 18 optimizations
- Skipped: 3 (require manual review or --aggressive flag)
- Estimated improvement: 35-40% faster page loads

Next steps:
1. Run 'wheels reload' to apply changes
2. Test application thoroughly
3. Monitor performance metrics

Dry Run Mode

Preview changes without applying them:

wheels optimize performance --dry-run

Optimization Preview
====================

Would apply the following changes:

1. Database: Create index on users.email
   SQL: CREATE INDEX idx_users_email ON users(email);

2. Cache: Enable action caching for ProductsController.index
   File: /app/controllers/ProductsController.cfc
   Add: <cfset caches(action="index", time=30)>

3. Query: Optimize Product.search()
   File: /app/models/Product.cfc
   Change: Add query hint for index usage

[... more changes ...]

Run without --dry-run to apply these optimizations.

Aggressive Mode

Enables optimizations that may change application behavior:

wheels optimize performance --aggressive

Additional aggressive optimizations:
✓ Converted synchronous operations to async
✓ Implemented aggressive query result caching
✓ Reduced session timeout to 20 minutes
✓ Disabled debug output in production
✓ Implemented database connection pooling
⚠️ Changed default query timeout to 10 seconds

Backup and Rollback

Backups are created automatically:

Backup created: /backups/optimize-backup-20240115-143022.zip

To rollback:
wheels optimize rollback --backup=optimize-backup-20240115-143022.zip

Configuration

Customize optimization behavior in /config/optimizer.json:

{
  "database": {
    "autoIndex": true,
    "indexThreshold": 1000,
    "queryTimeout": 30
  },
  "caching": {
    "defaultDuration": 3600,
    "cacheQueries": true
  },
  "assets": {
    "minify": true,
    "compress": true,
    "cdnEnabled": false
  }
}

Notes

  • Always test optimizations in a staging environment first

  • Some optimizations require application restart

  • Monitor application after applying optimizations

  • Use wheels analyze performance to measure improvements

  • Aggressive optimizations should be carefully reviewed

wheels docs serve

Serves generated documentation locally with live reload for development and review.

Usage

wheels docs serve [--port=<port>] [--host=<host>] [--open] [--watch]

Parameters

  • --port - (Optional) Port number to serve on. Default: 4000

  • --host - (Optional) Host to bind to. Default: localhost

  • --open - (Optional) Open browser automatically after starting

  • --watch - (Optional) Watch for changes and regenerate. Default: true

Description

The docs serve command starts a local web server to preview your generated documentation. It includes:

  • Live reload on documentation changes

  • Search functionality

  • Responsive design preview

  • Print-friendly styling

  • Offline access support

Examples

Basic documentation server

wheels docs serve

Serve on different port

wheels docs serve --port=8080

Open in browser automatically

wheels docs serve --open

Serve without watching for changes

wheels docs serve --no-watch

Bind to all interfaces

wheels docs serve --host=0.0.0.0

Server Output

Starting documentation server...
================================

Configuration:
- Documentation path: /docs/generated/
- Server URL: http://localhost:4000
- Live reload: enabled
- File watching: enabled

Server started successfully!
- Local: http://localhost:4000
- Network: http://192.168.1.100:4000

Press Ctrl+C to stop the server

[2024-01-15 14:30:22] Serving documentation...
[2024-01-15 14:30:45] GET / - 200 OK (15ms)
[2024-01-15 14:30:46] GET /models/user.html - 200 OK (8ms)
[2024-01-15 14:31:02] File changed: /app/models/User.cfc
[2024-01-15 14:31:02] Regenerating documentation...
[2024-01-15 14:31:05] Documentation updated - reloading browsers

Features

Live Reload

When --watch is enabled, the server:

  • Monitors source files for changes

  • Automatically regenerates affected documentation

  • Refreshes browser without manual reload

Search Functionality

  • Full-text search across all documentation

  • Instant results as you type

  • Keyboard navigation (Ctrl+K or Cmd+K)

  • Search history

Navigation

Documentation Structure:
/                     # Home page with overview
/models/              # All models documentation
/models/user.html     # Specific model docs
/controllers/         # Controller documentation  
/api/                 # API reference
/guides/              # Custom guides
/search               # Search page

Print Support

  • Optimized CSS for printing

  • Clean layout without navigation

  • Page breaks at logical points

  • Print entire docs or single pages

Development Workflow

Typical usage during development:

# Terminal 1: Start the docs server
wheels docs serve --open

# Terminal 2: Make code changes
# Edit your models/controllers
# Documentation auto-updates

# Terminal 3: Generate fresh docs if needed
wheels docs generate

Review workflow:

# Generate and serve for team review
wheels docs generate --format=html
wheels docs serve --port=3000 --host=0.0.0.0

# Share URL with team
echo "Documentation available at http://$(hostname -I | awk '{print $1}'):3000"

Configuration

Server Configuration

Create /config/docs-server.json:

{
  "server": {
    "port": 4000,
    "host": "localhost",
    "baseUrl": "/docs",
    "cors": true
  },
  "watch": {
    "enabled": true,
    "paths": ["app/", "config/"],
    "ignore": ["*.log", "temp/"],
    "delay": 1000
  },
  "features": {
    "search": true,
    "print": true,
    "offline": true,
    "analytics": false
  }
}

Custom Headers

{
  "headers": {
    "Cache-Control": "no-cache",
    "X-Frame-Options": "SAMEORIGIN",
    "Content-Security-Policy": "default-src 'self'"
  }
}

Access Control

Basic Authentication

wheels docs serve --auth=username:password

IP Restrictions

wheels docs serve --allow="192.168.1.0/24,10.0.0.0/8"

Troubleshooting

Common Issues

Port already in use:

Error: Port 4000 is already in use

# Solution: Use a different port
wheels docs serve --port=4001

Cannot access from network:

# Bind to all interfaces
wheels docs serve --host=0.0.0.0

# Check firewall settings

Documentation not updating:

# Force regeneration
wheels docs generate --force
wheels docs serve --watch

Notes

  • Server is intended for development/review only

  • For production, deploy static files to web server

  • Large documentation sets may take time to generate

  • Browser must support JavaScript for search

  • Offline mode caches documentation locally

wheels docs generate

Generates documentation for your CFWheels application from code comments and annotations.

Usage

wheels docs generate [--format=<format>] [--output=<path>] [--include=<patterns>] [--theme=<theme>]

Parameters

  • --format - (Optional) Output format: html, markdown, json. Default: html

  • --output - (Optional) Output directory path. Default: /docs/generated

  • --include - (Optional) Patterns to include: all, api, models, controllers. Default: all

  • --theme - (Optional) Documentation theme: default, minimal, gitbook. Default: default

Description

The docs generate command automatically creates comprehensive documentation from your CFWheels application by parsing:

  • JavaDoc-style comments in CFCs

  • Model relationships and validations

  • Controller actions and routes

  • Configuration files

  • Database schema

  • API endpoints

Examples

Generate complete documentation

wheels docs generate

Generate markdown docs

wheels docs generate --format=markdown

Generate API documentation only

wheels docs generate --include=api --output=/public/api-docs

Use GitBook theme

wheels docs generate --theme=gitbook

Generate for specific components

wheels docs generate --include="models,controllers"

Documentation Sources

Model Documentation

/**
 * User model for authentication and authorization
 * 
 * @author John Doe
 * @since 1.0.0
 */
component extends="Model" {
    
    /**
     * Initialize user relationships and validations
     * @hint Sets up the user model configuration
     */
    function config() {
        // Relationships
        hasMany("orders");
        belongsTo("role");
        
        // Validations
        validatesPresenceOf("email,firstName,lastName");
        validatesUniquenessOf("email");
    }
    
    /**
     * Find active users with recent activity
     * 
     * @param days Number of days to look back
     * @return query Active users
     */
    public query function findActive(numeric days=30) {
        return findAll(
            where="lastLoginAt >= :date",
            params={date: dateAdd("d", -arguments.days, now())}
        );
    }
}

Controller Documentation

/**
 * Handles user management operations
 * 
 * @displayname User Controller
 * @namespace /users
 */
component extends="Controller" {
    
    /**
     * Display paginated list of users
     * 
     * @hint GET /users
     * @access public
     * @return void
     */
    function index() {
        param name="params.page" default="1";
        users = model("user").findAll(
            page=params.page,
            perPage=20,
            order="createdAt DESC"
        );
    }
}

Generated Output

HTML Format

/docs/generated/
├── index.html
├── assets/
│   ├── css/
│   └── js/
├── models/
│   ├── index.html
│   └── user.html
├── controllers/
│   ├── index.html
│   └── users.html
├── api/
│   └── endpoints.html
└── database/
    └── schema.html

Documentation includes:

  • Overview: Application structure and architecture

  • Models: Properties, methods, relationships, validations

  • Controllers: Actions, filters, routes

  • API Reference: Endpoints, parameters, responses

  • Database Schema: Tables, columns, indexes

  • Configuration: Settings and environment variables

Output Example

Generating documentation...
========================

Scanning source files... ✓
✓ Found 15 models
✓ Found 12 controllers  
✓ Found 45 routes
✓ Found 8 API endpoints

Processing documentation...
✓ Extracting comments
✓ Building relationships
✓ Generating diagrams
✓ Creating index

Writing documentation...
✓ HTML files generated
✓ Assets copied
✓ Search index created

Documentation generated successfully!
- Output: /docs/generated/
- Files: 82
- Size: 2.4 MB

View documentation:
- Local: file:///path/to/docs/generated/index.html
- Serve: wheels docs serve

Documentation Features

Auto-generated Content

  • Class hierarchies and inheritance

  • Method signatures and parameters

  • Property types and defaults

  • Relationship diagrams

  • Route mappings

  • Database ERD

Search Functionality

// Built-in search in HTML docs
{
  "searchable": true,
  "index": "lunr",
  "fields": ["title", "content", "tags"]
}

Custom Themes

Configure in /config/docs-theme.json:

{
  "theme": "custom",
  "colors": {
    "primary": "#007bff",
    "secondary": "#6c757d"
  },
  "logo": "/assets/logo.png",
  "favicon": "/assets/favicon.ico",
  "customCSS": "/assets/custom.css"
}

Integration

CI/CD Documentation

# Generate docs on every commit
- name: Generate Docs
  run: |
    wheels docs generate --format=html
    wheels docs generate --format=markdown --output=wiki/

Git Hooks

#!/bin/bash
# pre-commit hook
wheels docs generate --include=api --format=json
git add docs/api/openapi.json

Notes

  • Documentation is generated from code comments

  • Use consistent JavaDoc format for best results

  • Private methods are excluded by default

  • Images and diagrams are auto-generated

  • Supports custom templates and themes

wheels analyze code
wheels analyze performance
wheels security scan
wheels test
wheels plugins list
wheels plugins install
wheels plugins remove
Plugin Development Guide
wheels env
wheels env list
wheels env setup
wheels reload

wheels analyze security

⚠️ DEPRECATED: This command has been deprecated. Please use wheels security scan instead.

Migration Notice

The analyze security command has been moved to provide better organization and expanded functionality.

Old Command

wheels analyze security

New Command

wheels security scan

Why the Change?

  • Better command organization with dedicated security namespace

  • Enhanced scanning capabilities

  • Improved reporting options

  • Integration with security vulnerability databases

See Also

Deprecation Timeline

  • Deprecated: v1.5.0

  • Warning Added: v1.6.0

  • Removal Planned: v2.0.0

The command currently redirects to wheels security scan with a deprecation warning.

wheels optimize

Base command for application optimization.

Synopsis

wheels optimize [subcommand] [options]

Description

The wheels optimize command provides tools to improve your Wheels application's performance. It analyzes and optimizes various aspects including database queries, asset delivery, caching strategies, and code execution.

Subcommands

Command
Description

performance

Comprehensive performance optimization

Options

Option
Description

--help

Show help information

--version

Show version information

Direct Usage

When called without subcommands, runs automatic optimizations:

wheels optimize

This performs:

  1. Database query optimization

  2. Asset minification and bundling

  3. Cache configuration

  4. Code optimization

Examples

Run all optimizations

wheels optimize

Optimize with detailed output

wheels optimize --verbose

Dry run to preview changes

wheels optimize --dry-run

Optimize specific areas

wheels optimize --only=database,assets

Optimization Areas

Database

  • Index analysis and creation

  • Query optimization

  • Connection pooling

  • Cache configuration

Assets

  • JavaScript minification

  • CSS optimization

  • Image compression

  • Bundle creation

Caching

  • Query cache setup

  • Page cache configuration

  • Object cache optimization

  • CDN integration

Code

  • Dead code elimination

  • Function optimization

  • Memory usage reduction

  • Startup time improvement

Output Example

Wheels Application Optimization
==============================

Analyzing application...
✓ Scanned 245 files
✓ Analyzed 1,234 queries
✓ Checked 456 assets

Optimizations Applied:
---------------------

Database (4 optimizations)
  ✓ Added index on users.email
  ✓ Added composite index on orders(user_id, created_at)
  ✓ Optimized slow query in ProductModel.cfc
  ✓ Enabled query caching for static queries

Assets (3 optimizations)
  ✓ Minified 23 JavaScript files (saved 145KB)
  ✓ Optimized 15 CSS files (saved 67KB)
  ✓ Compressed 34 images (saved 2.3MB)

Caching (2 optimizations)
  ✓ Configured Redis for object caching
  ✓ Enabled page caching for static routes

Code (3 optimizations)
  ✓ Removed 12 unused functions
  ✓ Optimized application startup
  ✓ Reduced memory footprint by 15%

Performance Impact:
  - Page load time: -32% (2.4s → 1.6s)
  - Database queries: -45% (avg 23ms → 12ms)
  - Memory usage: -15% (512MB → 435MB)
  - Startup time: -20% (8s → 6.4s)

Configuration

Configure via .wheels-optimize.json:

{
  "optimize": {
    "database": {
      "autoIndex": true,
      "queryCache": true,
      "slowQueryThreshold": 100
    },
    "assets": {
      "minify": true,
      "bundle": true,
      "compress": true,
      "compressionQuality": 85
    },
    "cache": {
      "provider": "redis",
      "ttl": 3600,
      "autoEvict": true
    },
    "code": {
      "removeDeadCode": true,
      "optimizeStartup": true,
      "memoryOptimization": true
    }
  }
}

Optimization Strategies

Development Mode

Focuses on developer experience:

wheels optimize --env=development
  • Faster rebuilds

  • Source maps preserved

  • Debug information retained

Production Mode

Maximum performance:

wheels optimize --env=production
  • Aggressive minification

  • Dead code elimination

  • Maximum compression

Balanced Mode

Balance between size and debuggability:

wheels optimize --mode=balanced

Advanced Options

Selective Optimization

# Only database optimizations
wheels optimize --only=database

# Exclude asset optimization
wheels optimize --skip=assets

# Specific optimizations
wheels optimize --optimizations=minify,compress,index

Performance Budgets

# Set performance budgets
wheels optimize --budget-js=200kb --budget-css=50kb

# Fail if budgets exceeded
wheels optimize --strict-budgets

Integration

Build Process

{
  "scripts": {
    "build": "wheels optimize --env=production",
    "build:dev": "wheels optimize --env=development"
  }
}

CI/CD Pipeline

- name: Optimize application
  run: |
    wheels optimize --env=production
    wheels optimize --verify

Optimization Reports

Generate Report

wheels optimize --report=optimization-report.html

Report Contents

  • Before/after metrics

  • Optimization details

  • Performance graphs

  • Recommendations

Rollback

If optimizations cause issues:

# Create backup before optimizing
wheels optimize --backup

# Rollback to backup
wheels optimize --rollback

# Rollback specific optimization
wheels optimize --rollback=database

Best Practices

  1. Test After Optimization: Verify functionality

  2. Monitor Performance: Track real-world impact

  3. Incremental Approach: Optimize gradually

  4. Keep Backups: Enable rollback capability

  5. Document Changes: Track what was optimized

Performance Monitoring

After optimization:

# Verify optimizations
wheels optimize --verify

# Performance benchmark
wheels optimize --benchmark

# Compare before/after
wheels optimize --compare

Common Issues

Over-optimization

  • Breaking functionality

  • Debugging difficulties

  • Longer build times

Solutions

  • Use balanced mode

  • Keep source maps in development

  • Test thoroughly

Use Cases

  1. Pre-deployment: Optimize before production

  2. Performance Issues: Fix slow applications

  3. Cost Reduction: Reduce resource usage

  4. User Experience: Improve load times

  5. Scalability: Prepare for growth

Notes

  • Always backup before optimization

  • Some optimizations require restart

  • Monitor application after optimization

  • Optimization impact varies by application

See Also

wheels security

Base command for security management and vulnerability scanning.

Synopsis

wheels security [subcommand] [options]

Description

The wheels security command provides comprehensive security tools for Wheels applications. It scans for vulnerabilities, checks security configurations, and helps implement security best practices.

Subcommands

Command
Description

scan

Scan for security vulnerabilities

Options

Option
Description

--help

Show help information

--version

Show version information

Direct Usage

When called without subcommands, performs a quick security check:

wheels security

Output:

Wheels Security Overview
=======================

Last Scan: 2024-01-15 10:30:45
Status: 3 issues found

Critical: 0
High:     1 
Medium:   2
Low:      0

Vulnerabilities:
- [HIGH] SQL Injection risk in UserModel.cfc:45
- [MEDIUM] Missing CSRF protection on /admin routes
- [MEDIUM] Outdated dependency: cfml-jwt (2.1.0 → 3.0.0)

Run 'wheels security scan' for detailed analysis

Examples

Quick security check

wheels security

Check security status

wheels security --status

Generate security report

wheels security --report

Check specific area

wheels security --check=dependencies

Security Areas

Code Security

  • SQL injection detection

  • XSS vulnerability scanning

  • Path traversal checks

  • Command injection risks

Configuration

  • Security headers

  • CORS settings

  • Authentication config

  • Session management

Dependencies

  • Vulnerable packages

  • Outdated libraries

  • License compliance

  • Supply chain risks

Infrastructure

  • SSL/TLS configuration

  • Port exposure

  • File permissions

  • Environment secrets

Security Configuration

Configure via .wheels-security.json:

{
  "security": {
    "scanOnCommit": true,
    "autoFix": false,
    "severity": "medium",
    "ignore": [
      {
        "rule": "sql-injection",
        "file": "legacy/*.cfc",
        "reason": "Legacy code, sandboxed"
      }
    ],
    "checks": {
      "dependencies": true,
      "code": true,
      "configuration": true,
      "infrastructure": true
    }
  }
}

Security Policies

Define Policies

Create .wheels-security-policy.yml:

policies:
  - name: "No Direct SQL"
    description: "Prevent direct SQL execution"
    severity: "high"
    rules:
      - pattern: "queryExecute\\(.*\\$.*\\)"
        message: "Use parameterized queries"
  
  - name: "Secure Headers"
    description: "Require security headers"
    severity: "medium"
    headers:
      - "X-Frame-Options"
      - "X-Content-Type-Options"
      - "Content-Security-Policy"

Policy Enforcement

# Check policy compliance
wheels security --check-policy

# Enforce policies (fail on violation)
wheels security --enforce-policy

Integration

Git Hooks

.git/hooks/pre-commit:

#!/bin/bash
wheels security scan --severity=high --fail-on-issues

CI/CD Pipeline

- name: Security scan
  run: |
    wheels security scan --format=sarif
    wheels security --upload-results

IDE Integration

{
  "wheels.security": {
    "realTimeScan": true,
    "showInlineWarnings": true
  }
}

Security Headers

Check Headers

wheels security headers --check

Configure Headers

// Application.cfc
this.securityHeaders = {
    "X-Frame-Options": "DENY",
    "X-Content-Type-Options": "nosniff",
    "Strict-Transport-Security": "max-age=31536000",
    "Content-Security-Policy": "default-src 'self'"
};

Dependency Scanning

Check Dependencies

wheels security deps

Update Vulnerable Dependencies

wheels security deps --fix

License Compliance

wheels security licenses --allowed=MIT,Apache-2.0

Security Fixes

Automatic Fixes

# Fix auto-fixable issues
wheels security fix

# Fix specific issue types
wheels security fix --type=headers,csrf

Manual Fixes

The command provides guidance:

Issue: SQL Injection Risk
File: /models/User.cfc:45
Fix: Replace direct SQL with parameterized query

Current:
query = "SELECT * FROM users WHERE id = #arguments.id#";

Suggested:
queryExecute(
    "SELECT * FROM users WHERE id = :id",
    { id: arguments.id }
);

Security Reports

Generate Reports

# HTML report
wheels security scan --report=html

# JSON report for tools
wheels security scan --format=json

# SARIF for GitHub
wheels security scan --format=sarif

Report Contents

  • Executive summary

  • Detailed findings

  • Remediation steps

  • Compliance status

  • Trend analysis

Compliance

Standards

Check compliance with standards:

# OWASP Top 10
wheels security compliance --standard=owasp-top10

# PCI DSS
wheels security compliance --standard=pci-dss

# Custom standard
wheels security compliance --standard=./company-standard.yml

Security Monitoring

Continuous Monitoring

# Start monitoring
wheels security monitor --start

# Check monitor status
wheels security monitor --status

# View alerts
wheels security monitor --alerts

Alert Configuration

{
  "monitoring": {
    "alerts": {
      "email": "security@example.com",
      "slack": "https://hooks.slack.com/...",
      "severity": "high"
    }
  }
}

Security Best Practices

  1. Regular Scans: Schedule automated scans

  2. Fix Quickly: Address high-severity issues immediately

  3. Update Dependencies: Keep libraries current

  4. Security Training: Educate development team

  5. Defense in Depth: Layer security measures

Common Vulnerabilities

SQL Injection

// Vulnerable
query = "SELECT * FROM users WHERE id = #url.id#";

// Secure
queryExecute(
    "SELECT * FROM users WHERE id = :id",
    { id: { value: url.id, cfsqltype: "integer" } }
);

XSS

// Vulnerable
<cfoutput>#form.userInput#</cfoutput>

// Secure
<cfoutput>#encodeForHTML(form.userInput)#</cfoutput>

Emergency Response

Incident Detection

# Check for compromise indicators
wheels security incident --check

# Generate incident report
wheels security incident --report

Lockdown Mode

# Enable security lockdown
wheels security lockdown --enable

# Disable after resolution
wheels security lockdown --disable

Notes

  • Security scans may take time on large codebases

  • Some checks require running application

  • False positives should be documented

  • Regular updates improve detection accuracy

See Also

wheels docker init

Initialize Docker configuration for your Wheels application.

Synopsis

wheels docker init [options]

Description

The wheels docker init command creates Docker configuration files for containerizing your Wheels application. It generates a Dockerfile, docker-compose.yml, and supporting configuration files optimized for Wheels applications.

Options

Option
Description
Default

--engine

CFML engine (lucee5, lucee6, adobe2018, adobe2021, adobe2023)

lucee6

--database

Database system (mysql, postgresql, sqlserver, none)

mysql

--port

Application port

8080

--with-nginx

Include Nginx reverse proxy

false

--with-redis

Include Redis for caching

false

--production

Generate production-ready configuration

false

--force

Overwrite existing Docker files

false

--help

Show help information

Examples

Basic initialization

wheels docker init

Initialize with Adobe ColdFusion

wheels docker init --engine=adobe2023

Production setup with Nginx

wheels docker init --production --with-nginx --port=80

Initialize with PostgreSQL

wheels docker init --database=postgresql

Full stack with Redis

wheels docker init --with-nginx --with-redis --database=postgresql

What It Does

  1. Creates Dockerfile optimized for CFML applications:

    • Base image selection based on engine

    • Dependency installation

    • Application file copying

    • Environment configuration

  2. Generates docker-compose.yml with:

    • Application service

    • Database service (if selected)

    • Nginx service (if selected)

    • Redis service (if selected)

    • Network configuration

    • Volume mappings

  3. Additional files:

    • .dockerignore - Excludes unnecessary files

    • docker-entrypoint.sh - Container startup script

    • Configuration files for selected services

Generated Files

Dockerfile Example

FROM ortussolutions/commandbox:lucee5

# Set working directory
WORKDIR /app

# Copy application files
COPY . /app

# Install dependencies
RUN box install

# Expose port
EXPOSE 8080

# Start server
CMD ["box", "server", "start", "--console"]

docker-compose.yml Example

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - WHEELS_ENV=development
      - DB_HOST=database
    depends_on:
      - database
    volumes:
      - ./:/app

  database:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=wheels
      - MYSQL_DATABASE=wheels_app
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

Configuration Options

Development Mode

  • Hot reload enabled

  • Source code mounted as volume

  • Debug ports exposed

  • Development databases

Production Mode

  • Optimized image size

  • Security hardening

  • Health checks

  • Restart policies

  • Resource limits

Use Cases

  1. Local Development: Consistent development environment across team

  2. Testing: Isolated test environments with different configurations

  3. CI/CD: Containerized testing in pipelines

  4. Deployment: Production-ready containers for cloud deployment

Environment Variables

Common environment variables configured:

Variable
Description

WHEELS_ENV

Application environment

WHEELS_DATASOURCE

Database connection name

DB_HOST

Database hostname

DB_PORT

Database port

DB_NAME

Database name

DB_USER

Database username

DB_PASSWORD

Database password

Notes

  • Requires Docker and Docker Compose installed

  • Database passwords are set to defaults in development

  • Production configurations should use secrets management

  • The command detects existing Docker files and prompts before overwriting

See Also

wheels ci init

Initialize continuous integration configuration for your Wheels application.

Synopsis

wheels ci init [provider] [options]

Description

The wheels ci init command sets up continuous integration (CI) configuration files for your Wheels application. It generates CI/CD pipeline configurations for popular CI providers like GitHub Actions, GitLab CI, Jenkins, and others.

Arguments

Argument
Description
Default

provider

CI provider to configure (github, gitlab, jenkins, travis, circle)

github

Options

Option
Description

--template

Use a specific template (basic, full, minimal)

--branch

Default branch name

--engines

CFML engines to test (lucee5, lucee6, adobe2018, adobe2021, adobe2023)

--databases

Databases to test against (h2, mysql, postgresql, sqlserver)

--force

Overwrite existing CI configuration

--help

Show help information

Examples

Initialize GitHub Actions

wheels ci init github

Initialize with specific engines

wheels ci init github --engines=lucee6,adobe2023

Initialize with multiple databases

wheels ci init github --databases=mysql,postgresql

Initialize GitLab CI

wheels ci init gitlab --branch=develop

Use full template with force overwrite

wheels ci init github --template=full --force

What It Does

  1. Creates CI configuration files in the appropriate location:

    • GitHub Actions: .github/workflows/ci.yml

    • GitLab CI: .gitlab-ci.yml

    • Jenkins: Jenkinsfile

    • Travis CI: .travis.yml

    • CircleCI: .circleci/config.yml

  2. Configures test matrix for:

    • Multiple CFML engines

    • Multiple database systems

    • Different operating systems (if supported)

  3. Sets up common CI tasks:

    • Dependency installation

    • Database setup

    • Test execution

    • Code coverage reporting

    • Artifact generation

Generated Configuration

Example GitHub Actions configuration:

name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        cfengine: [lucee@5, lucee@6, adobe@2023]
        database: [h2]
    
    steps:
      - uses: actions/checkout@v4
      - name: Setup CommandBox
        uses: ortus-solutions/setup-commandbox@v2
      - name: Install dependencies
        run: box install
      - name: Start server
        run: box server start cfengine=${{ matrix.cfengine }}
      - name: Run tests
        run: box testbox run

Templates

Basic Template

  • Single engine and database

  • Essential test execution

  • Minimal configuration

Full Template

  • Multiple engines and databases

  • Code coverage

  • Deployment steps

  • Notifications

Minimal Template

  • Bare minimum for CI

  • Quick setup

  • No extras

Use Cases

  1. New Project Setup: Quickly add CI/CD to a new Wheels project

  2. Migration: Move from one CI provider to another

  3. Standardization: Apply consistent CI configuration across projects

  4. Multi-Engine Testing: Ensure compatibility across CFML engines

Notes

  • Requires a valid Wheels application structure

  • Some providers may require additional authentication setup

  • Database services are configured as Docker containers where possible

  • The command respects existing .gitignore patterns

See Also

wheels docs

Base command for documentation generation and management.

Note: This command is currently broken. Use subcommands directly.

Synopsis

wheels docs [subcommand] [options]

Description

The wheels docs command provides documentation tools for Wheels applications. It generates API documentation, manages inline documentation, and serves documentation locally. While the base command is currently broken, the subcommands generate and serve work correctly.

Subcommands

Command
Description
Status

generate

Generate documentation

✓ Working

serve

Serve documentation locally

✓ Working

Options

Option
Description

--help

Show help information

--version

Show version information

Current Status

The base wheels docs command is temporarily unavailable due to a known issue. Please use the subcommands directly:

  • wheels docs generate - Generate documentation

  • wheels docs serve - Serve documentation

Expected Behavior (When Fixed)

When operational, running wheels docs without subcommands would:

  1. Check for existing documentation

  2. Generate if missing or outdated

  3. Provide quick access links

  4. Show documentation statistics

Expected output:

Wheels Documentation Status
==========================

Generated: 2024-01-15 10:30:45
Files: 156 documented (89%)
Coverage: 1,234 of 1,456 functions

Recent Changes:
- UserModel.cfc: 5 new methods
- OrderController.cfc: Updated examples
- config/routes.cfm: New route docs

Quick Actions:
- View docs: http://localhost:4000
- Regenerate: wheels docs generate
- Check coverage: wheels docs coverage

Workaround

Until the base command is fixed, use this workflow:

# Generate documentation
wheels docs generate

# Serve documentation
wheels docs serve

# Or combine in one line
wheels docs generate && wheels docs serve

Documentation System

Supported Formats

  • JavaDoc-style comments

  • Markdown files

  • Inline documentation

  • README files

Documentation Sources

/app/
├── models/          # Model documentation
├── controllers/     # Controller documentation
├── views/          # View helpers documentation
├── config/         # Configuration docs
├── docs/           # Additional documentation
└── README.md       # Project overview

Configuration

Configure in .wheels-docs.json:

{
  "docs": {
    "output": "./documentation",
    "format": "html",
    "theme": "wheels-default",
    "include": [
      "app/**/*.cfc",
      "app/**/*.cfm",
      "docs/**/*.md"
    ],
    "exclude": [
      "vendor/**",
      "tests/**"
    ],
    "options": {
      "private": false,
      "inherited": true,
      "examples": true
    }
  }
}

Documentation Comments

Component Documentation

/**
 * User model for authentication and profile management
 * 
 * @author John Doe
 * @since 1.0.0
 */
component extends="Model" {
    
    /**
     * Authenticate user with credentials
     * 
     * @username The user's username or email
     * @password The user's password
     * @return User object if authenticated, false otherwise
     * 
     * @example
     * user = model("User").authenticate("john@example.com", "secret");
     * if (isObject(user)) {
     *     // Login successful
     * }
     */
    public any function authenticate(required string username, required string password) {
        // Implementation
    }
}

Inline Documentation

<!--- 
    @doc
    This view displays the user profile page
    
    @requires User object in 'user' variable
    @layout layouts/main
--->

Integration

Auto-generation

Set up automatic documentation generation:

// package.json
{
  "scripts": {
    "docs:build": "wheels docs generate",
    "docs:watch": "wheels docs generate --watch",
    "docs:serve": "wheels docs serve"
  }
}

CI/CD

- name: Generate documentation
  run: |
    wheels docs generate
    wheels docs coverage --min=80

When to Use Subcommands

Generate Documentation

Use when:

  • Adding new components

  • Updating documentation comments

  • Before releases

  • Setting up documentation site

wheels docs generate

Serve Documentation

Use when:

  • Reviewing documentation

  • Local development

  • Team documentation access

  • API exploration

wheels docs serve

Troubleshooting

Base Command Not Working

Error: "wheels docs command is broken"

Solution: Use subcommands directly:

wheels docs generate
wheels docs serve

Missing Documentation

If documentation is not generated:

  1. Check file patterns in config

  2. Verify comment format

  3. Look for syntax errors

  4. Check exclude patterns

Future Plans

The base command will be restored to provide:

  • Documentation dashboard

  • Coverage reports

  • Quick statistics

  • Change detection

  • Auto-serve option

Notes

  • Subcommands work independently

  • Documentation is generated incrementally

  • Large projects may take time to document

  • Keep documentation comments updated

See Also

wheels deploy

Base command for deployment operations in Wheels applications.

Synopsis

Description

The wheels deploy command provides a comprehensive deployment system for Wheels applications. It manages the entire deployment lifecycle including initialization, execution, monitoring, and rollback capabilities.

Subcommands

Options

Examples

View deployment help

Initialize deployment

Execute deployment

Check deployment status

Deployment Workflow

  1. Initialize: Set up deployment configuration

  2. Setup: Prepare deployment environment

  3. Configure: Set secrets and proxy settings

  4. Deploy: Push and execute deployment

  5. Monitor: Check status and logs

  6. Rollback (if needed):

Configuration

Deployment configuration is stored in .wheels-deploy.json:

Deployment Strategies

Rolling Deployment

  • Zero-downtime deployment

  • Gradual rollout

  • Automatic rollback on failure

Blue-Green Deployment

  • Two identical environments

  • Instant switching

  • Easy rollback

Canary Deployment

  • Gradual traffic shifting

  • Risk mitigation

  • Performance monitoring

Environment Variables

Use Cases

  1. Continuous Deployment: Automated deployments from CI/CD

  2. Manual Releases: Controlled production deployments

  3. Multi-Environment: Deploy to staging, production, etc.

  4. Disaster Recovery: Quick rollback capabilities

  5. Scheduled Deployments: Deploy during maintenance windows

Best Practices

  1. Always run deploy audit before production deployments

  2. Use deploy lock during critical operations

  3. Configure proper hooks for migrations and cache clearing

  4. Keep deployment logs for auditing

  5. Test deployments in staging first

  6. Use secrets management for sensitive data

Notes

  • Requires SSH access for remote deployments

  • Git repository must be properly configured

  • Database backups recommended before deployment

  • Monitor application health after deployment

See Also

wheels deploy init

Initialize deployment configuration for your Wheels application.

Synopsis

Description

The wheels deploy init command creates and configures deployment settings for your Wheels application. It generates deployment configuration files and sets up target environments.

Arguments

Options

Examples

Interactive initialization

Initialize production target

Initialize with Git deployment

Initialize Docker deployment

Initialize with specific strategy

What It Does

  1. Creates deployment configuration:

    • .wheels-deploy.json in project root

    • Target-specific settings

    • Deployment credentials (encrypted)

  2. Sets up deployment structure:

    • Release directories

    • Shared directories (uploads, logs)

    • Symbolic links

  3. Configures deployment hooks:

    • Pre-deployment tasks

    • Post-deployment tasks

    • Rollback procedures

  4. Validates configuration:

    • Tests connection to target

    • Verifies permissions

    • Checks dependencies

Configuration Structure

Generated .wheels-deploy.json:

Deployment Types

SSH Deployment

  • Secure shell access

  • Rsync for file transfer

  • Full control over deployment

FTP Deployment

  • Legacy support

  • Simple file transfer

  • Limited automation

Git Deployment

  • Git-based workflows

  • Post-receive hooks

  • Version control integration

Docker Deployment

  • Container orchestration

  • Image-based deployment

  • Scalable infrastructure

Shared Resources

Shared directories and files persist across deployments:

  • Directories: User uploads, logs, cache

  • Files: Environment configs, secrets

Deployment Hooks

Pre-deployment

  • Build assets

  • Run tests

  • Backup database

Post-deployment

  • Run migrations

  • Clear caches

  • Restart services

  • Send notifications

Rollback

  • Restore previous release

  • Revert database

  • Clear caches

Interactive Mode

When run without arguments, the command enters interactive mode:

Security Considerations

  1. Credentials: Stored encrypted in config

  2. SSH Keys: Recommended over passwords

  3. Permissions: Least privilege principle

  4. Secrets: Use environment variables

Use Cases

  1. New Project: Set up deployment pipeline

  2. Migration: Move from manual to automated deployment

  3. Multi-Environment: Configure staging and production

  4. Team Setup: Share deployment configuration

Notes

  • Run from project root directory

  • Requires appropriate server access

  • Test with staging environment first

  • Back up existing configuration before overwriting

See Also

wheels docker deploy

Deploy your Wheels application using Docker containers.

Synopsis

Description

The wheels docker deploy command deploys your containerized Wheels application to various Docker environments including Docker Swarm, Kubernetes, or cloud container services.

Arguments

Options

Examples

Deploy locally

Deploy to Docker Swarm

Deploy to Kubernetes

Deploy to AWS ECS

Dry run deployment

Deploy with resource limits

What It Does

  1. Build and Tag:

    • Builds Docker image if needed

    • Tags with specified version

    • Validates image integrity

  2. Push to Registry:

    • Authenticates with registry

    • Pushes image to registry

    • Verifies push success

  3. Deploy to Target:

    • Generates deployment manifests

    • Applies configuration

    • Monitors deployment status

    • Performs health checks

  4. Post-Deployment:

    • Runs database migrations

    • Clears caches

    • Sends notifications

Deployment Targets

Local

  • Uses docker-compose

  • Development/testing

  • No registry required

Docker Swarm

  • Creates/updates services

  • Load balancing

  • Rolling updates

  • Secrets management

Kubernetes

  • Creates deployments, services, ingress

  • ConfigMaps and Secrets

  • Horizontal pod autoscaling

  • Rolling updates

AWS ECS

  • Task definitions

  • Service updates

  • Load balancer configuration

  • Auto-scaling

Google Cloud Run

  • Serverless containers

  • Automatic scaling

  • HTTPS endpoints

Azure Container Instances

  • Container groups

  • Managed instances

  • Integration with Azure services

Configuration Files

Kubernetes Example (k8s.yml)

Docker Swarm Example (swarm.yml)

Environment Management

Environment variables can be provided via:

  1. --env-file option

  2. Platform-specific secrets

  3. Configuration files

  4. Command line overrides

Health Checks

The deployment includes health checks:

  • Readiness probes

  • Liveness probes

  • Startup probes

  • Custom health endpoints

Rollback

To rollback a deployment:

Or manually:

Use Cases

  1. Staging Deployments: Test production configurations

  2. Production Releases: Deploy new versions with zero downtime

  3. Scaling: Adjust replicas based on load

  4. Multi-Region: Deploy to multiple regions/zones

  5. Blue-Green Deployments: Switch between environments

Notes

  • Ensure Docker images are built before deployment

  • Registry authentication must be configured

  • Database migrations should be handled separately or via init containers

  • Monitor deployment logs for troubleshooting

  • Use --dry-run to preview changes before applying

Troubleshooting

Common issues:

  • Image not found: Ensure image is pushed to registry

  • Auth failures: Check registry credentials

  • Resource limits: Adjust CPU/memory settings

  • Port conflicts: Check service port mappings

See Also

wheels deploy exec

Execute a deployment to the specified target environment.

Synopsis

Description

The wheels deploy exec command performs the actual deployment of your Wheels application to a configured target environment. It handles file synchronization, runs deployment hooks, and manages the deployment lifecycle.

Arguments

Options

Examples

Deploy to production

Deploy specific tag

Deploy with dry run

Force deployment

Deploy without hooks

Verbose deployment

Deployment Process

  1. Pre-flight Checks:

    • Verify target configuration

    • Check Git status

    • Validate dependencies

    • Test connectivity

  2. Preparation:

    • Create release directory

    • Export code from repository

    • Install dependencies

    • Compile assets

  3. Synchronization:

    • Upload files to target

    • Exclude ignored files

    • Preserve shared resources

  4. Activation:

    • Update symbolic links

    • Run database migrations

    • Clear caches

    • Reload application

  5. Cleanup:

    • Remove old releases

    • Clean temporary files

    • Update deployment log

Output Example

Deployment Strategies

Rolling Deployment

  • Gradual rollout

  • Zero downtime

  • Automatic rollback on failure

Blue-Green Deployment

  • Instant switching

  • Full rollback capability

  • Requires double resources

Canary Deployment

  • Gradual traffic shift

  • Risk mitigation

  • Performance monitoring

Hook Execution

Pre-deployment Hooks

Executed before deployment:

Post-deployment Hooks

Executed after activation:

Rollback Handling

If deployment fails:

  1. Automatic rollback triggered

  2. Previous release restored

  3. Rollback hooks executed

  4. Notifications sent

Manual rollback:

Environment Variables

Available during deployment:

Dry Run Mode

Preview deployment without changes:

Shows:

  • Files to be transferred

  • Hooks to be executed

  • Resources to be created

  • Estimated deployment time

Error Handling

Common errors and solutions:

  1. Connection Failed

    • Check SSH keys/credentials

    • Verify network connectivity

    • Confirm server accessibility

  2. Permission Denied

    • Check user permissions

    • Verify directory ownership

    • Review deployment path

  3. Hook Failed

    • Check hook commands

    • Verify dependencies

    • Review error logs

  4. Disk Space

    • Check available space

    • Clean old releases

    • Review keep-releases setting

Performance Optimization

  • Use --skip-assets if assets pre-built

  • Enable compression for transfers

  • Parallelize hook execution

  • Use incremental deployments

Monitoring

Track deployment metrics:

  • Deployment duration

  • Transfer size

  • Success/failure rate

  • Rollback frequency

Use Cases

  1. Automated Deployment: CI/CD pipeline integration

  2. Scheduled Releases: Deploy during maintenance windows

  3. Emergency Hotfix: Quick production patches

  4. Feature Deployment: Deploy specific features

  5. A/B Testing: Deploy variants for testing

Notes

  • Always test in staging first

  • Monitor application after deployment

  • Keep deployment logs for auditing

  • Have rollback plan ready

  • Coordinate with team for production deployments

See Also

wheels deploy hooks

Manage deployment hooks for custom actions during the deployment lifecycle.

Synopsis

Description

The wheels deploy hooks command allows you to manage custom hooks that execute at specific points during the deployment process. These hooks enable you to run custom scripts, notifications, or integrations at key deployment stages.

Actions

  • list - List all configured deployment hooks

  • add - Add a new deployment hook

  • remove - Remove an existing hook

  • enable - Enable a disabled hook

  • disable - Disable a hook without removing it

  • test - Test a hook execution

  • show - Show details about a specific hook

Options

  • --stage - Deployment stage (pre-deploy, post-deploy, rollback, error)

  • --script - Path to hook script or command

  • --timeout - Hook execution timeout in seconds (default: 300)

  • --retry - Number of retry attempts on failure (default: 0)

  • --async - Run hook asynchronously

  • --environment, -e - Target environment (default: all)

  • --priority - Hook execution priority (1-100, lower runs first)

Hook Stages

pre-deploy

Executed before deployment starts:

  • Backup creation

  • Service notifications

  • Resource validation

  • Custom checks

post-deploy

Executed after successful deployment:

  • Cache warming

  • Health checks

  • Monitoring updates

  • Success notifications

rollback

Executed during rollback operations:

  • Cleanup tasks

  • State restoration

  • Failure notifications

  • Recovery actions

error

Executed when deployment fails:

  • Error logging

  • Alert notifications

  • Cleanup operations

  • Incident creation

Examples

List all hooks

Add a pre-deploy hook

Add notification hook

Test a hook

Disable a hook temporarily

Show hook details

Hook Script Requirements

Hook scripts should:

  • Return exit code 0 for success

  • Return non-zero exit code for failure

  • Output status messages to stdout

  • Output errors to stderr

  • Handle timeouts gracefully

Example hook script

Environment Variables

Hooks receive these environment variables:

  • DEPLOY_STAGE - Current deployment stage

  • DEPLOY_ENVIRONMENT - Target environment

  • DEPLOY_VERSION - Version being deployed

  • DEPLOY_USER - User initiating deployment

  • DEPLOY_TIMESTAMP - Deployment start time

Use Cases

Database backup hook

Notification hooks

Health check hook

Best Practices

  1. Keep hooks simple: Each hook should do one thing well

  2. Handle failures: Always include error handling in hook scripts

  3. Set timeouts: Prevent hooks from blocking deployments

  4. Test thoroughly: Test hooks in staging before production

  5. Log output: Ensure hooks provide clear logging

  6. Use priorities: Order hooks appropriately with priorities

  7. Document hooks: Maintain documentation for all hooks

See Also

wheels deploy audit

Audit deployment configuration and security settings to ensure compliance and best practices.

Synopsis

Description

The wheels deploy audit command performs a comprehensive security and configuration audit of your deployment setup. It checks for common misconfigurations, security vulnerabilities, and compliance issues in your deployment environment.

Options

  • --environment, -e - Target environment to audit (default: production)

  • --report-format - Output format for audit report (json, html, text) (default: text)

  • --output, -o - Save audit report to file

  • --severity - Minimum severity level to report (low, medium, high, critical)

  • --fix - Attempt to automatically fix issues where possible

  • --verbose, -v - Show detailed audit information

Examples

Basic audit

Audit staging environment

Generate HTML report

Show only high severity issues

Auto-fix issues

Audit Checks

The command performs the following audit checks:

Security

  • SSL/TLS configuration

  • Exposed sensitive files

  • Default credentials

  • Authentication mechanisms

  • Authorization settings

  • Input validation

  • Session management

  • Error handling

Configuration

  • Environment variables

  • Database connections

  • API endpoints

  • File permissions

  • Resource limits

  • Logging configuration

  • Backup settings

  • Monitoring setup

Compliance

  • Data protection requirements

  • Access control policies

  • Audit trail completeness

  • Retention policies

  • Encryption standards

Output

The audit generates a detailed report including:

  • Summary of findings

  • Issue severity levels

  • Affected components

  • Remediation recommendations

  • Compliance status

  • Performance metrics

Use Cases

Pre-deployment audit

Scheduled audits

Compliance reporting

Best Practices

  1. Regular audits: Run audits regularly, not just before deployments

  2. Fix critical issues: Always address critical and high severity issues

  3. Document exceptions: Keep records of accepted risks and exceptions

  4. Automate checks: Integrate audits into your CI/CD pipeline

  5. Review reports: Have security team review audit reports

Integration

The audit command integrates with:

  • CI/CD pipelines for automated security checks

  • Monitoring systems for continuous compliance

  • Issue tracking systems for remediation workflow

  • Reporting tools for compliance documentation

See Also

- The replacement command with enhanced features

- Detailed performance optimization

- Performance analysis

- Cache management

- Configuration management

- Detailed security scanning

- Security analysis (deprecated)

- Deploy using Docker

- General deployment commands

- Initialize CI configuration

- Run tests locally

- Initialize Docker configuration

- Deployment commands

- Generate documentation

- Serve documentation

Command
Description
Option
Description
Variable
Description

- Initialize deployment

- Execute deployment

- Rollback deployment

- Docker deployments

Argument
Description
Default
Option
Description
Default

- Deployment overview

- Setup deployment environment

- Execute deployment

- Manage secrets

Argument
Description
Default
Option
Description
Default

- Initialize Docker configuration

- General deployment commands

- Push deployments

Argument
Description
Default
Option
Description
Default
Variable
Description

- Deployment overview

- Check deployment status

- Rollback deployment

- View deployment logs

- Execute deployment

- Rollback deployment

- View deployment logs

- Check deployment status

- Run security scans

- Setup deployment environment

security scan
wheels optimize performance
wheels analyze performance
wheels cache
wheels config
wheels security scan
wheels analyze security
Security Best Practices
OWASP Guidelines
wheels docker deploy
wheels deploy
wheels ci init
wheels test
wheels docker init
wheels deploy
wheels docs generate
wheels docs serve
Documentation Best Practices
API Documentation Guide
wheels deploy [subcommand] [options]

audit

Audit deployment configuration and security

exec

Execute a deployment

hooks

Manage deployment hooks

init

Initialize deployment configuration

lock

Lock deployment state

logs

View deployment logs

proxy

Configure deployment proxy

push

Push deployment to target

rollback

Rollback to previous deployment

secrets

Manage deployment secrets

setup

Setup deployment environment

status

Check deployment status

stop

Stop active deployment

--help

Show help information

--version

Show version information

wheels deploy --help
wheels deploy init
wheels deploy exec production
wheels deploy status
wheels deploy init
wheels deploy setup production
wheels deploy secrets set DB_PASSWORD
wheels deploy proxy configure
wheels deploy push
wheels deploy exec
wheels deploy status
wheels deploy logs --follow
wheels deploy rollback
{
  "targets": {
    "production": {
      "host": "prod.example.com",
      "path": "/var/www/app",
      "branch": "main",
      "hooks": {
        "pre-deploy": ["npm run build"],
        "post-deploy": ["wheels dbmigrate latest"]
      }
    },
    "staging": {
      "host": "staging.example.com",
      "path": "/var/www/staging",
      "branch": "develop"
    }
  },
  "defaults": {
    "strategy": "rolling",
    "keepReleases": 5
  }
}

WHEELS_DEPLOY_TARGET

Default deployment target

WHEELS_DEPLOY_STRATEGY

Default deployment strategy

WHEELS_DEPLOY_TIMEOUT

Deployment timeout in seconds

wheels deploy init [target] [options]

target

Deployment target name (production, staging, dev)

Interactive prompt

--type

Deployment type (ssh, ftp, rsync, git, docker)

ssh

--host

Target host or server

Interactive prompt

--port

Connection port

22 (SSH), 21 (FTP)

--path

Remote deployment path

/var/www/html

--user

Remote user

Current user

--branch

Git branch to deploy

main

--strategy

Deployment strategy (rolling, blue-green, canary)

rolling

--keep-releases

Number of releases to keep

5

--force

Overwrite existing configuration

false

--help

Show help information

wheels deploy init
wheels deploy init production --host=prod.example.com --path=/var/www/app
wheels deploy init staging --type=git --branch=develop
wheels deploy init production --type=docker --host=swarm.example.com
wheels deploy init production --strategy=blue-green --keep-releases=3
{
  "version": "1.0",
  "targets": {
    "production": {
      "type": "ssh",
      "host": "prod.example.com",
      "port": 22,
      "user": "deploy",
      "path": "/var/www/app",
      "branch": "main",
      "strategy": "rolling",
      "keepReleases": 5,
      "shared": {
        "dirs": ["logs", "uploads", "temp"],
        "files": [".env", "config/production.json"]
      },
      "hooks": {
        "pre-deploy": [
          "npm run build",
          "box install --production"
        ],
        "post-deploy": [
          "wheels dbmigrate latest",
          "wheels reload production",
          "npm run cache:clear"
        ],
        "rollback": [
          "wheels reload production"
        ]
      },
      "exclude": [
        ".git",
        ".gitignore",
        "node_modules",
        "tests",
        "*.log"
      ]
    }
  },
  "defaults": {
    "timeout": 300,
    "retries": 3,
    "notifications": {
      "slack": {
        "webhook": "https://hooks.slack.com/..."
      }
    }
  }
}
? Select deployment target: (Use arrow keys)
❯ production
  staging
  development
  + Add new target

? Deployment type: (Use arrow keys)
❯ SSH (Recommended)
  Git
  Docker
  FTP

? Target host: prod.example.com
? Remote path: /var/www/app
? Remote user: deploy
wheels docker deploy [target] [options]

target

Deployment target (local, swarm, kubernetes, ecs, gcp, azure)

local

--tag

Docker image tag

latest

--registry

Docker registry URL

Docker Hub

--namespace

Kubernetes namespace or project name

default

--replicas

Number of replicas

1

--cpu

CPU limit (e.g., "0.5", "2")

Platform default

--memory

Memory limit (e.g., "512Mi", "2Gi")

Platform default

--env-file

Environment file path

.env

--config-file

Deployment configuration file

Auto-detect

--dry-run

Preview deployment without applying

false

--force

Force deployment even if up-to-date

false

--help

Show help information

wheels docker deploy local
wheels docker deploy swarm --replicas=3
wheels docker deploy kubernetes --namespace=production --tag=v1.2.3
wheels docker deploy ecs --registry=123456789.dkr.ecr.us-east-1.amazonaws.com
wheels docker deploy kubernetes --dry-run
wheels docker deploy swarm --cpu=2 --memory=4Gi --replicas=5
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wheels-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wheels
  template:
    metadata:
      labels:
        app: wheels
    spec:
      containers:
      - name: app
        image: myregistry/wheels-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: WHEELS_ENV
          value: production
version: '3.8'
services:
  app:
    image: myregistry/wheels-app:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
    ports:
      - "80:8080"
    secrets:
      - db_password
wheels docker deploy [target] --rollback
# Kubernetes
kubectl rollout undo deployment/wheels-app

# Docker Swarm
docker service rollback wheels-app
wheels deploy exec [target] [options]

target

Deployment target (production, staging, etc.)

Required

--tag

Git tag or commit to deploy

Latest commit

--branch

Git branch to deploy

Configured branch

--force

Force deployment even with uncommitted changes

false

--skip-hooks

Skip pre/post deployment hooks

false

--skip-tests

Skip test execution

false

--skip-assets

Skip asset compilation

false

--dry-run

Preview deployment without executing

false

--verbose

Show detailed output

false

--timeout

Deployment timeout in seconds

300

--help

Show help information

wheels deploy exec production
wheels deploy exec production --tag=v1.2.3
wheels deploy exec staging --dry-run
wheels deploy exec production --force
wheels deploy exec staging --skip-hooks
wheels deploy exec production --verbose
Deploying to production...
✓ Pre-flight checks passed
✓ Creating release 20240115120000
✓ Exporting code from main branch
✓ Installing dependencies
✓ Compiling assets
✓ Uploading files to prod.example.com
  → Transferred 1,234 files (45.6 MB)
✓ Running pre-deployment hooks
  → npm run build
  → box install --production
✓ Activating release
✓ Running post-deployment hooks
  → wheels dbmigrate latest
  → wheels reload production
✓ Cleaning up old releases
✓ Deployment completed successfully!

Deployment Summary:
- Target: production
- Release: 20240115120000
- Duration: 2m 34s
- Status: SUCCESS
wheels deploy exec production
wheels deploy exec production --strategy=blue-green
wheels deploy exec production --strategy=canary --percentage=10
"pre-deploy": [
  "npm test",
  "npm run build",
  "box install --production"
]
"post-deploy": [
  "wheels dbmigrate latest",
  "wheels reload production",
  "npm run cache:clear",
  "curl -X POST https://api.example.com/deploy-notification"
]
wheels deploy rollback production

WHEELS_DEPLOY_TARGET

Target environment name

WHEELS_DEPLOY_RELEASE

Release timestamp

WHEELS_DEPLOY_BRANCH

Git branch

WHEELS_DEPLOY_TAG

Git tag (if specified)

WHEELS_DEPLOY_USER

User executing deployment

wheels deploy exec production --dry-run
wheels deploy hooks <action> [hook-name] [options]
wheels deploy hooks list
wheels deploy hooks add backup-database \
  --stage pre-deploy \
  --script ./scripts/backup-db.sh \
  --timeout 600
wheels deploy hooks add slack-notify \
  --stage post-deploy \
  --script "curl -X POST https://hooks.slack.com/services/..." \
  --async
wheels deploy hooks test backup-database
wheels deploy hooks disable backup-database
wheels deploy hooks show slack-notify
#!/bin/bash
# pre-deploy-backup.sh

echo "Starting database backup..."
pg_dump myapp > backup-$(date +%Y%m%d-%H%M%S).sql

if [ $? -eq 0 ]; then
    echo "Backup completed successfully"
    exit 0
else
    echo "Backup failed" >&2
    exit 1
fi
wheels deploy hooks add db-backup \
  --stage pre-deploy \
  --script ./hooks/backup-database.sh \
  --timeout 1800
# Slack notification
wheels deploy hooks add notify-slack \
  --stage post-deploy \
  --script ./hooks/notify-slack.sh \
  --async

# Email notification
wheels deploy hooks add notify-email \
  --stage error \
  --script ./hooks/send-error-email.sh
wheels deploy hooks add health-check \
  --stage post-deploy \
  --script ./hooks/verify-health.sh \
  --retry 3
wheels deploy audit [options]
wheels deploy audit
wheels deploy audit --environment staging
wheels deploy audit --report-format html --output audit-report.html
wheels deploy audit --severity high
wheels deploy audit --fix
# Run comprehensive audit before deploying
wheels deploy audit --severity low
wheels deploy push --if-audit-passes
# Run regular audits in CI/CD
wheels deploy audit --output reports/audit-$(date +%Y%m%d).json
# Generate compliance report
wheels deploy audit --report-format html --output compliance.html
wheels deploy init
wheels deploy exec
wheels deploy rollback
wheels docker deploy
wheels deploy
wheels deploy setup
wheels deploy exec
wheels deploy secrets
wheels docker init
wheels deploy
wheels deploy push
wheels deploy
wheels deploy status
wheels deploy rollback
wheels deploy logs
deploy exec
deploy rollback
deploy logs
deploy status
security scan
deploy setup

wheels deploy proxy

Configure and manage deployment proxy settings for routing traffic during deployments.

Synopsis

wheels deploy proxy <action> [options]

Description

The wheels deploy proxy command manages proxy configurations for zero-downtime deployments, traffic routing, and load balancing during deployment operations. It handles blue-green deployments, canary releases, and traffic splitting strategies.

Actions

  • config - View or update proxy configuration

  • route - Manage traffic routing rules

  • health - Check proxy and backend health

  • switch - Switch traffic between deployments

  • rollback - Revert proxy to previous configuration

  • status - Show current proxy status

Options

  • --environment, -e - Target environment (default: production)

  • --strategy - Deployment strategy (blue-green, canary, rolling)

  • --weight - Traffic weight percentage for canary deployments

  • --backend - Backend server or service identifier

  • --health-check - Health check endpoint

  • --timeout - Proxy timeout in seconds

  • --sticky-sessions - Enable session affinity

  • --ssl-redirect - Force SSL redirect

Deployment Strategies

Blue-Green Deployment

# Configure blue-green proxy
wheels deploy proxy config --strategy blue-green

# Switch traffic to green
wheels deploy proxy switch --to green

# Rollback to blue if needed
wheels deploy proxy rollback

Canary Deployment

# Start canary with 10% traffic
wheels deploy proxy route --strategy canary --weight 10

# Increase to 50%
wheels deploy proxy route --weight 50

# Full deployment
wheels deploy proxy route --weight 100

Rolling Deployment

# Configure rolling updates
wheels deploy proxy config --strategy rolling --batch-size 25

Examples

Configure proxy settings

wheels deploy proxy config \
  --health-check /health \
  --timeout 30 \
  --ssl-redirect

View proxy status

wheels deploy proxy status

Set up canary deployment

# Deploy new version to canary
wheels deploy proxy route \
  --strategy canary \
  --backend app-v2 \
  --weight 5

# Monitor metrics...

# Gradually increase traffic
wheels deploy proxy route --weight 25
wheels deploy proxy route --weight 50
wheels deploy proxy route --weight 100

Health check configuration

wheels deploy proxy health \
  --health-check /api/health \
  --interval 10 \
  --timeout 5 \
  --retries 3

Traffic Routing Rules

Weight-based routing

# Split traffic 80/20
wheels deploy proxy route \
  --backend app-v1 --weight 80 \
  --backend app-v2 --weight 20

Header-based routing

# Route beta users to new version
wheels deploy proxy route \
  --rule "header:X-Beta-User=true" \
  --backend app-v2

Geographic routing

# Route by region
wheels deploy proxy route \
  --rule "geo:region=us-west" \
  --backend app-us-west

Proxy Configuration

Load balancing

# Configure load balancing algorithm
wheels deploy proxy config \
  --load-balancer round-robin \
  --health-check /health

Session affinity

# Enable sticky sessions
wheels deploy proxy config \
  --sticky-sessions \
  --session-cookie "app_session"

SSL/TLS settings

# Configure SSL
wheels deploy proxy config \
  --ssl-redirect \
  --ssl-protocols "TLSv1.2,TLSv1.3" \
  --ssl-ciphers "HIGH:!aNULL"

Use Cases

Zero-downtime deployment

# Deploy new version alongside current
wheels deploy exec --target green

# Verify new deployment
wheels deploy proxy health --backend green

# Switch traffic
wheels deploy proxy switch --to green

# Remove old version
wheels deploy stop --target blue

A/B testing

# Set up A/B test
wheels deploy proxy route \
  --backend feature-a --weight 50 \
  --backend feature-b --weight 50 \
  --cookie "ab_test"

Gradual rollout

# Start with internal users
wheels deploy proxy route \
  --rule "ip:10.0.0.0/8" \
  --backend app-v2

# Expand to beta users
wheels deploy proxy route \
  --rule "header:X-Beta=true" \
  --backend app-v2

# Full rollout
wheels deploy proxy switch --to app-v2

Monitoring

The proxy provides metrics for:

  • Request count and latency

  • Error rates

  • Backend health status

  • Traffic distribution

  • Connection pool status

View metrics

wheels deploy proxy status --metrics

Export metrics

wheels deploy proxy status --format prometheus > metrics.txt

Best Practices

  1. Always health check: Configure health checks for all backends

  2. Gradual rollouts: Start with small traffic percentages

  3. Monitor metrics: Watch error rates during transitions

  4. Test rollback: Ensure rollback procedures work

  5. Document rules: Keep routing rules well-documented

  6. Use sticky sessions carefully: They can affect load distribution

  7. SSL everywhere: Always use SSL in production

Troubleshooting

Backend not receiving traffic

# Check health status
wheels deploy proxy health --backend app-v2

# Verify routing rules
wheels deploy proxy status --rules

# Check proxy logs
wheels deploy logs --component proxy

High error rates

# Check backend health
wheels deploy proxy health --all

# Reduce traffic to problematic backend
wheels deploy proxy route --backend app-v2 --weight 0

# Investigate logs
wheels deploy logs --grep "proxy error"

See Also

wheels deploy secrets

Manage deployment secrets and sensitive configuration.

Synopsis

wheels deploy secrets [action] [name] [value] [options]

Description

The wheels deploy secrets command provides secure management of sensitive data like API keys, database passwords, and other credentials used during deployment. Secrets are encrypted and stored separately from your codebase.

Actions

Action
Description

list

List all secrets for a target

set

Set or update a secret

get

Retrieve a secret value

delete

Remove a secret

sync

Synchronize secrets with target

rotate

Rotate secret values

export

Export secrets to file

import

Import secrets from file

Arguments

Argument
Description
Required

action

Action to perform

Yes

name

Secret name

For set/get/delete

value

Secret value

For set action

Options

Option
Description
Default

--target

Deployment target

production

--env-file

Environment file for bulk operations

--format

Output format (table, json, dotenv)

table

--force

Skip confirmation prompts

false

--encrypt

Encryption method (aes256, rsa)

aes256

--key-file

Path to encryption key

.wheels-deploy-key

--help

Show help information

Examples

Set a secret

wheels deploy secrets set DB_PASSWORD mySecretPass123 --target=production

Set secret interactively (hidden input)

wheels deploy secrets set API_KEY --target=production
# Prompts for value without displaying it

List all secrets

wheels deploy secrets list --target=production

Get a specific secret

wheels deploy secrets get DB_PASSWORD --target=production

Delete a secret

wheels deploy secrets delete OLD_API_KEY --target=production

Import from .env file

wheels deploy secrets import --env-file=.env.production --target=production

Export secrets

wheels deploy secrets export --target=production --format=dotenv > .env.backup

Rotate database password

wheels deploy secrets rotate DB_PASSWORD --target=production

Secret Storage

Secrets are stored encrypted in:

  • Local: .wheels-deploy-secrets/[target].enc

  • Remote: Deployment target's secure storage

Encryption

Secrets are encrypted using:

  • AES-256 encryption by default

  • Unique key per environment

  • Optional RSA public key encryption

Key Management

Encryption keys stored in:

.wheels-deploy-key        # Default key file
.wheels-deploy-key.pub    # Public key (RSA)
.wheels-deploy-key.priv   # Private key (RSA)

Secret Types

Environment Variables

Standard key-value pairs:

wheels deploy secrets set DATABASE_URL "mysql://user:pass@host/db"
wheels deploy secrets set REDIS_URL "redis://localhost:6379"

File-based Secrets

Store file contents as secrets:

wheels deploy secrets set SSL_CERT --file=/path/to/cert.pem
wheels deploy secrets set SSH_KEY --file=~/.ssh/id_rsa

Multi-line Secrets

wheels deploy secrets set PRIVATE_KEY --multiline
# Enter/paste content, end with Ctrl+D

Bulk Operations

Import from .env

# Import all variables from .env file
wheels deploy secrets import --env-file=.env.production

# Import with prefix
wheels deploy secrets import --env-file=.env --prefix=APP_

Export Formats

Table format:

wheels deploy secrets list

JSON format:

wheels deploy secrets list --format=json

DotEnv format:

wheels deploy secrets export --format=dotenv

Secret Rotation

Rotate secrets with automatic update:

# Rotate with auto-generated value
wheels deploy secrets rotate DB_PASSWORD

# Rotate with custom value
wheels deploy secrets rotate API_KEY --value=newKey123

# Rotate multiple secrets
wheels deploy secrets rotate DB_PASSWORD,REDIS_PASSWORD,API_KEY

Synchronization

Sync secrets to deployment target:

# Sync all secrets
wheels deploy secrets sync --target=production

# Verify sync status
wheels deploy secrets sync --target=production --dry-run

Access Control

Team Sharing

Share encrypted secrets with team:

# Export encrypted secrets
wheels deploy secrets export --target=production --encrypted > secrets.enc

# Import on another machine
wheels deploy secrets import --file=secrets.enc --key-file=team-key

Permission Levels

  • Read: View secret names only

  • Write: Set/update secrets

  • Admin: Delete/rotate secrets

Integration

During Deployment

Secrets automatically injected:

{
  "hooks": {
    "pre-deploy": [
      "wheels deploy secrets sync"
    ]
  }
}

In Application

Access secrets via environment:

<cfset dbPassword = env("DB_PASSWORD", "")>
<cfset apiKey = env("API_KEY", "")>

Security Best Practices

  1. Never commit secrets to version control

  2. Use strong encryption keys

  3. Rotate secrets regularly

  4. Limit access to production secrets

  5. Audit secret usage via logs

  6. Use different secrets per environment

Backup and Recovery

Backup Secrets

# Encrypted backup
wheels deploy secrets export --target=production --encrypted > backup-$(date +%Y%m%d).enc

# Secure offsite backup
wheels deploy secrets export | gpg -c > secrets.gpg

Restore Secrets

# From encrypted backup
wheels deploy secrets import --file=backup-20240115.enc

# From GPG encrypted file
gpg -d secrets.gpg | wheels deploy secrets import

Troubleshooting

Common Issues

  1. Encryption key not found:

    wheels deploy secrets init --generate-key
  2. Permission denied:

    • Check file permissions on key files

    • Verify user has deployment access

  3. Secret not available during deployment:

    • Ensure secrets are synced

    • Check target configuration

Use Cases

  1. Database Credentials: Secure database passwords

  2. API Keys: Third-party service credentials

  3. SSL Certificates: Secure certificate storage

  4. OAuth Secrets: Client secrets for OAuth

  5. Encryption Keys: Application encryption keys

Notes

  • Secrets are never logged or displayed in plain text

  • Use environment-specific secrets

  • Regular rotation improves security

  • Keep encryption keys secure and backed up

  • Monitor secret access in production

See Also

wheels deploy lock

Lock deployment state to prevent concurrent deployments and maintain deployment integrity.

Synopsis

wheels deploy lock <action> [options]

Description

The wheels deploy lock command manages deployment locks to prevent concurrent deployments, ensure deployment atomicity, and maintain system stability during critical operations. This is essential for coordinating deployments in team environments and automated systems.

Actions

  • acquire - Acquire a deployment lock

  • release - Release an existing lock

  • status - Check current lock status

  • force-release - Force release a stuck lock (use with caution)

  • list - List all active locks

  • wait - Wait for lock to become available

Options

  • --environment, -e - Target environment to lock (default: production)

  • --reason - Reason for acquiring lock (required for acquire)

  • --duration - Lock duration in minutes (default: 30)

  • --wait-timeout - Maximum time to wait for lock in seconds

  • --force - Force acquire lock even if already locked

  • --owner - Lock owner identifier (default: current user)

  • --metadata - Additional lock metadata as JSON

Examples

Acquire deployment lock

wheels deploy lock acquire --reason "Deploying version 2.1.0"

Check lock status

wheels deploy lock status

Release lock

wheels deploy lock release

Wait for lock availability

wheels deploy lock wait --wait-timeout 300

Force release stuck lock

wheels deploy lock force-release --reason "Previous deployment crashed"

List all locks

wheels deploy lock list

Lock Information

Locks contain the following information:

  • Lock ID

  • Environment

  • Owner (user/system)

  • Acquisition time

  • Expiration time

  • Reason

  • Associated deployment ID

  • Metadata

Lock Types

Manual locks

User-initiated locks for maintenance or manual deployments:

wheels deploy lock acquire --reason "Database maintenance" --duration 60

Automatic locks

System-acquired locks during automated deployments:

# Automatically acquired during deployment
wheels deploy exec

Emergency locks

High-priority locks for critical operations:

wheels deploy lock acquire --force --reason "Emergency hotfix"

Use Cases

Maintenance window

# Lock environment for maintenance
wheels deploy lock acquire \
  --reason "Scheduled maintenance" \
  --duration 120 \
  --metadata '{"ticket": "MAINT-123"}'

# Perform maintenance...

# Release when done
wheels deploy lock release

Coordinated deployment

# Wait for lock and deploy
wheels deploy lock wait --wait-timeout 600
wheels deploy exec --auto-lock

CI/CD integration

# In CI/CD pipeline
if wheels deploy lock acquire --reason "CI/CD Deploy #${BUILD_ID}"; then
    wheels deploy exec
    wheels deploy lock release
else
    echo "Could not acquire lock"
    exit 1
fi

Lock States

Available

No active lock, deployments can proceed

Locked

Active lock in place, deployments blocked

Expired

Lock duration exceeded, can be cleaned up

Force-locked

Emergency lock overriding normal locks

Best Practices

  1. Always provide reasons: Clear reasons help team coordination

  2. Set appropriate durations: Don't lock longer than necessary

  3. Release locks promptly: Release as soon as operation completes

  4. Handle lock failures: Plan for scenarios when locks can't be acquired

  5. Monitor stuck locks: Set up alerts for long-running locks

  6. Use force sparingly: Only force-release when absolutely necessary

  7. Document lock usage: Keep records of lock operations

Error Handling

Common lock errors and solutions:

Lock already exists

# Check who owns the lock
wheels deploy lock status

# Wait for it to be released
wheels deploy lock wait

# Or coordinate with lock owner

Lock expired during operation

# Extend lock duration if still needed
wheels deploy lock acquire --extend

Cannot release lock

# Verify you own the lock
wheels deploy lock status --verbose

# Force release if necessary
wheels deploy lock force-release --reason "Lock owner unavailable"

Integration

The lock system integrates with:

  • CI/CD pipelines for automated deployments

  • Monitoring systems for lock alerts

  • Deployment tools for automatic locking

  • Team communication tools for notifications

See Also

wheels deploy push

Push deployment artifacts to target environment.

Synopsis

wheels deploy push [options]

Description

The wheels deploy push command transfers deployment artifacts, configuration files, and application code to the target deployment environment. It handles file synchronization, artifact validation, and ensures secure transfer of deployment packages.

Options

  • --environment, -e - Target environment (default: production)

  • --artifact - Path to deployment artifact or directory

  • --config - Configuration file to include

  • --exclude - Files/patterns to exclude from deployment

  • --dry-run - Simulate push without actual transfer

  • --force - Force push even if validation fails

  • --parallel - Number of parallel upload threads

  • --compress - Compression method (gzip, bzip2, none)

  • --checksum - Verify file integrity with checksums

  • --bandwidth - Limit bandwidth usage (e.g., "1M", "500K")

Examples

Basic push

wheels deploy push --environment production

Push specific artifact

wheels deploy push --artifact dist/app-v2.1.0.tar.gz

Dry run to see what would be pushed

wheels deploy push --dry-run

Push with exclusions

wheels deploy push --exclude "*.log,tmp/*,node_modules"

Limited bandwidth push

wheels deploy push --bandwidth 1M

Artifact Types

Application bundles

# Push application bundle
wheels deploy push --artifact app-bundle.tar.gz

Docker images

# Push Docker image
wheels deploy push --artifact myapp:v2.1.0 --type docker

Static assets

# Push static files
wheels deploy push --artifact public/ --compress gzip

Configuration files

# Push config separately
wheels deploy push --config production.env --encrypt

Push Process

  1. Validation: Verify artifacts and environment

  2. Compression: Compress files if specified

  3. Checksum: Generate integrity checksums

  4. Transfer: Upload to target environment

  5. Verification: Confirm successful transfer

  6. Notification: Report push status

Use Cases

CI/CD pipeline push

# Build and push in CI/CD
npm run build
wheels deploy push --artifact dist/ --environment staging

Multi-environment push

# Push to multiple environments
for env in staging production; do
  wheels deploy push --environment $env --artifact release.tar.gz
done

Incremental push

# Push only changed files
wheels deploy push --incremental --since "1 hour ago"

Secure push with encryption

# Encrypt sensitive files during push
wheels deploy push \
  --artifact app.tar.gz \
  --config secrets.env \
  --encrypt

Transfer Methods

Direct transfer

Default method for simple deployments:

wheels deploy push --method direct

S3 bucket transfer

For AWS deployments:

wheels deploy push \
  --method s3 \
  --bucket my-deploy-bucket \
  --artifact app.tar.gz

Registry push

For containerized applications:

wheels deploy push \
  --method registry \
  --registry hub.example.com \
  --artifact myapp:latest

Validation

The push command performs several validations:

Pre-push validation

  • Artifact integrity check

  • Environment accessibility

  • Space availability

  • Permission verification

Post-push validation

  • Transfer completion

  • Checksum verification

  • Artifact extraction test

  • Configuration validation

Progress Monitoring

# Show detailed progress
wheels deploy push --verbose

# Output example:
Uploading app-v2.1.0.tar.gz to production
[████████████████████████████████] 100% 45.2MB/45.2MB
✓ Upload complete
✓ Checksum verified
✓ Artifact validated

Error Handling

Retry failed pushes

# Auto-retry on failure
wheels deploy push --retry 3 --retry-delay 30

Resume interrupted push

# Resume from last checkpoint
wheels deploy push --resume

Rollback on failure

# Automatic rollback if push fails
wheels deploy push --rollback-on-failure

Best Practices

  1. Always validate: Use --dry-run before actual push

  2. Use checksums: Enable checksum verification

  3. Compress large artifacts: Reduce transfer time and bandwidth

  4. Exclude unnecessary files: Use .deployignore file

  5. Monitor transfer: Watch for errors during push

  6. Test in staging: Always push to staging before production

  7. Keep artifacts versioned: Use semantic versioning

Configuration

.deployignore file

# Files to exclude from deployment
*.log
*.tmp
.env.local
node_modules/
test/
docs/

Push configuration

# deploy.yml
push:
  compression: gzip
  checksum: sha256
  parallel: 4
  exclude:
    - "*.log"
    - "tmp/*"
  retry:
    attempts: 3
    delay: 30

Integration

Push operations integrate with:

  • CI/CD systems for automated deployments

  • Artifact repositories (Nexus, Artifactory)

  • Container registries (Docker Hub, ECR)

  • CDN services for static assets

  • Monitoring systems for transfer tracking

See Also

wheels deploy rollback

Rollback a deployment to a previous release.

Synopsis

wheels deploy rollback [target] [options]

Description

The wheels deploy rollback command reverts your application to a previous deployment release. It provides quick recovery from failed deployments or problematic releases by switching back to a known-good state.

Arguments

Argument
Description
Default

target

Deployment target to rollback

Required

Options

Option
Description
Default

--release

Specific release to rollback to

Previous release

--steps

Number of releases to rollback

1

--skip-hooks

Skip rollback hooks

false

--force

Force rollback without confirmation

false

--dry-run

Preview rollback without executing

false

--verbose

Show detailed output

false

--help

Show help information

Examples

Rollback to previous release

wheels deploy rollback production

Rollback multiple releases

wheels deploy rollback production --steps=2

Rollback to specific release

wheels deploy rollback production --release=20240114093045

Preview rollback

wheels deploy rollback production --dry-run

Force rollback without confirmation

wheels deploy rollback production --force

Rollback Process

  1. Validation:

    • Verify target configuration

    • Check available releases

    • Validate rollback target

  2. Confirmation:

    • Display current release

    • Show target release

    • Request confirmation (unless --force)

  3. Execution:

    • Switch symbolic links

    • Run rollback hooks

    • Restore shared resources

    • Clear caches

  4. Verification:

    • Test application health

    • Verify services running

    • Check error logs

Output Example

Rolling back production deployment...

Current Release: 20240115120000
Target Release:  20240114093045

Changes to be reverted:
- 45 files modified
- 3 database migrations
- 2 configuration changes

? Proceed with rollback? (y/N) y

✓ Switching to release 20240114093045
✓ Running rollback hooks
  → Reverting database migrations
  → Clearing application cache
  → Restarting services
✓ Verifying application health
✓ Rollback completed successfully!

Rollback Summary:
- From: 20240115120000
- To:   20240114093045
- Duration: 45s
- Status: SUCCESS

Available Releases

List available releases:

wheels deploy status production --releases

Output:

Available releases for production:
1. 20240115120000 (current)
2. 20240114093045
3. 20240113154522
4. 20240112101133
5. 20240111163421

Rollback Hooks

Configure rollback-specific hooks:

{
  "hooks": {
    "rollback": [
      "wheels dbmigrate down --steps=1",
      "wheels reload production",
      "npm run cache:clear",
      "curl -X POST https://api.example.com/rollback-notification"
    ]
  }
}

Database Rollback

Handling database changes during rollback:

  1. Automatic Migration Rollback:

    wheels dbmigrate down --to=20240114093045
  2. Manual Intervention:

    • Some changes may require manual rollback

    • Data migrations might not be reversible

    • Always backup before deployment

Rollback Strategies

Immediate Rollback

Quick switch to previous release:

wheels deploy rollback production

Staged Rollback

Gradual rollback with canary:

wheels deploy rollback production --strategy=canary --percentage=10

Blue-Green Rollback

Instant switch between environments:

wheels deploy rollback production --strategy=blue-green

Emergency Rollback

For critical situations:

# Skip all checks and hooks
wheels deploy rollback production --force --skip-hooks

# Direct symbolic link switch (last resort)
ssh deploy@prod.example.com "cd /var/www/app && ln -sfn releases/20240114093045 current"

Rollback Validation

After rollback, verify:

  1. Application Health:

    wheels deploy status production --health
  2. Service Status:

    ssh deploy@prod.example.com "systemctl status cfml-app"
  3. Error Logs:

    wheels deploy logs production --tail=100 --filter=error

Preventing Rollback Issues

  1. Keep Sufficient Releases:

    • Configure keepReleases appropriately

    • Don't set too low (minimum 3-5)

  2. Test Rollback Procedures:

    • Practice in staging environment

    • Document manual procedures

    • Automate where possible

  3. Database Considerations:

    • Design reversible migrations

    • Backup before deployment

    • Test rollback scenarios

Rollback Limitations

  • Shared files/directories not rolled back

  • User-uploaded content preserved

  • External service changes not reverted

  • Some database changes irreversible

Use Cases

  1. Failed Deployment: Immediate recovery from deployment failure

  2. Performance Issues: Revert problematic release

  3. Critical Bugs: Quick fix by reverting

  4. Testing Rollback: Verify rollback procedures work

  5. Compliance: Revert unauthorized changes

Monitoring After Rollback

  • Check application performance

  • Monitor error rates

  • Verify user functionality

  • Review system resources

  • Analyze root cause

Notes

  • Always investigate why rollback was needed

  • Document rollback incidents

  • Update deployment procedures based on learnings

  • Consider implementing better pre-deployment testing

  • Communicate rollback to stakeholders

See Also

wheels deploy logs

View and manage deployment logs for troubleshooting and monitoring.

Synopsis

wheels deploy logs [options]

Description

The wheels deploy logs command provides access to deployment logs, allowing you to view, search, and export logs from past and current deployments. This is essential for troubleshooting deployment issues, auditing deployment history, and monitoring deployment progress.

Options

  • --deployment-id, -d - Specific deployment ID to view logs for

  • --environment, -e - Filter logs by environment (default: all)

  • --tail, -f - Follow log output in real-time

  • --lines, -n - Number of lines to display (default: 100)

  • --since - Show logs since timestamp (e.g., "2023-01-01", "1h", "30m")

  • --until - Show logs until timestamp

  • --grep, -g - Filter logs by pattern (regex supported)

  • --level - Filter by log level (debug, info, warn, error)

  • --format - Output format (text, json, csv) (default: text)

  • --output, -o - Export logs to file

  • --no-color - Disable colored output

Examples

View recent deployment logs

wheels deploy logs

Follow current deployment logs

wheels deploy logs --tail

View specific deployment logs

wheels deploy logs --deployment-id dep-123456

Filter by time range

wheels deploy logs --since "1 hour ago" --until "30 minutes ago"

Search for errors

wheels deploy logs --grep "error|failed" --level error

Export logs to file

wheels deploy logs --deployment-id dep-123456 --output deployment.log

View logs in JSON format

wheels deploy logs --format json --lines 50

Log Levels

Logs are categorized by severity:

  • DEBUG: Detailed diagnostic information

  • INFO: General informational messages

  • WARN: Warning messages for potential issues

  • ERROR: Error messages for failures

  • FATAL: Critical errors causing deployment failure

Log Structure

Each log entry contains:

  • Timestamp

  • Log level

  • Deployment stage

  • Component/service

  • Message

  • Additional metadata

Example log entry:

2023-12-01 14:23:45 [INFO] [pre-deploy] [backup] Starting database backup
2023-12-01 14:23:47 [INFO] [pre-deploy] [backup] Backup completed successfully
2023-12-01 14:23:48 [INFO] [deploy] [app] Deploying application version 2.1.0
2023-12-01 14:23:52 [ERROR] [deploy] [app] Failed to start service: connection refused

Use Cases

Real-time monitoring

# Monitor ongoing deployment
wheels deploy logs --tail --deployment-id current

Troubleshooting failures

# Find errors in recent deployments
wheels deploy logs --since "1 day ago" --level error

# Search for specific error
wheels deploy logs --grep "database connection" --level error

Audit trail

# Export deployment logs for audit
wheels deploy logs \
  --since "2023-01-01" \
  --until "2023-12-31" \
  --format csv \
  --output audit-2023.csv

Performance analysis

# Find slow operations
wheels deploy logs --grep "took [0-9]+ seconds" | grep -E "took [0-9]{3,} seconds"

Advanced Filtering

Complex grep patterns

# Find database-related errors
wheels deploy logs --grep "database|sql|connection.*error"

# Find deployment timing
wheels deploy logs --grep "(started|completed|failed).*deploy"

Multiple filters

# Production errors in last hour
wheels deploy logs \
  --environment production \
  --level error \
  --since "1 hour ago"

Log aggregation

# Count errors by component
wheels deploy logs --level error --format json | \
  jq -r '.component' | sort | uniq -c

Log Retention

  • Logs are retained based on environment settings

  • Default retention periods:

    • Production: 90 days

    • Staging: 30 days

    • Development: 7 days

  • Archived logs available through backup systems

Best Practices

  1. Use appropriate filters: Narrow down logs to relevant entries

  2. Export important logs: Save logs for failed deployments

  3. Monitor in real-time: Use --tail for active deployments

  4. Regular log review: Periodically review logs for patterns

  5. Set up alerts: Configure alerts for error patterns

  6. Maintain log hygiene: Ensure logs are meaningful and not excessive

Integration

Log viewing integrates with:

  • Monitoring systems (Datadog, New Relic, etc.)

  • Log aggregation services (ELK stack, Splunk)

  • Alerting systems for error notifications

  • CI/CD pipelines for deployment history

Troubleshooting

No logs appearing

# Check deployment status
wheels deploy status

# Verify deployment ID
wheels deploy list --recent

Log export failing

# Check available disk space
df -h

# Try smaller time range or line limit
wheels deploy logs --lines 1000 --output partial.log

See Also

- Execute deployment

- Rollback deployment

- Check deployment status

- Initialize deployment

- Execute deployment

- Set configuration values

- Execute deployment

- Check deployment status

- Rollback deployment

- Execute deployment after push

- Check push status

- Rollback pushed artifacts

- Execute deployment

- Check deployment status

- View deployment logs

- Rollback migrations

- Check deployment status

- Execute deployment

- Audit deployment configuration

deploy exec
deploy rollback
deploy status
wheels deploy init
wheels deploy exec
wheels config set
Security Best Practices
deploy exec
deploy status
deploy rollback
deploy exec
deploy status
deploy rollback
wheels deploy exec
wheels deploy status
wheels deploy logs
wheels dbmigrate down
deploy status
deploy exec
deploy audit
Figure 1: Wheels congratulations screen