|
| 1 | +--- |
| 2 | +title: Running Wheels 4 the CommandBox Way |
| 3 | +slug: wheels-4-the-commandbox-way |
| 4 | +publishedAt: '2026-07-16T14:00:00.000Z' |
| 5 | +updatedAt: '2026-07-06T05:14:40.000Z' |
| 6 | +author: Peter Amiri |
| 7 | +tags: |
| 8 | + - wheels-4 |
| 9 | + - commandbox |
| 10 | + - tooling |
| 11 | + - ci |
| 12 | +categories: [] |
| 13 | +excerpt: >- |
| 14 | + CommandBox teams do not have to switch. The complete box-native workflow for |
| 15 | + Wheels 4 — install from ForgeBox, engine selection with cfengine, |
| 16 | + datasources via CFConfig, every framework task's in-app equivalent, CI with |
| 17 | + setup-commandbox, and an honest accounting of what the wheels CLI adds if |
| 18 | + you ever want it. |
| 19 | +coverImage: null |
| 20 | +--- |
| 21 | + |
| 22 | +If your team has used CommandBox for years — `box install` for packages, `box server start` for engines, `server.json` in every repo — Wheels 4's arrival with its own CLI probably read as a question mark over your whole setup. We've heard the wariness directly in [the discussions](https://github.com/wheels-dev/wheels/discussions/1838): *do we have to switch?* |
| 23 | + |
| 24 | +No. This post is the complete CommandBox-native workflow for Wheels 4 — install, serve, engines, datasources, framework tasks, CI, packages — with the support contract stated precisely, because "it mostly works" is not a contract. |
| 25 | + |
| 26 | +## The contract, stated precisely |
| 27 | + |
| 28 | +In Wheels 4, CommandBox is supported as a **server manager and package fetcher** — indefinitely. That means: |
| 29 | + |
| 30 | +| You want to… | CommandBox? | How | |
| 31 | +|---|---|---| |
| 32 | +| Install the framework | **Yes** | `box install wheels-base-template` | |
| 33 | +| Vendor the core into an existing app | **Yes** | `box install wheels-core` | |
| 34 | +| Run a server — Lucee, Adobe CF, or BoxLang | **Yes** | `box server start` (with `cfengine=` to pick) | |
| 35 | +| Manage datasources per project | **Yes** | CFConfig in `server.json` — the scaffold ships the module | |
| 36 | +| Scaffold, generate, migrate, test, seed, deploy from a terminal | **No** | those are `wheels` CLI commands — but every one has an in-app equivalent that works under CommandBox (below) | |
| 37 | + |
| 38 | +That last row is the one that used to feel like a wall, and isn't. The `wheels` CLI is a *client* of surfaces the framework exposes to every app regardless of what serves it. Your CommandBox-served app has all of them. |
| 39 | + |
| 40 | +## Install and serve |
| 41 | + |
| 42 | +```bash |
| 43 | +box |
| 44 | +mkdir myapp --cd |
| 45 | +install wheels-base-template |
| 46 | +server start |
| 47 | +``` |
| 48 | + |
| 49 | +`wheels-base-template` pulls `wheels-core` into `vendor/wheels/` automatically via its install path — the resulting app is structurally identical to what `wheels new` produces, because it's the same skeleton published to ForgeBox. `server start` reads the shipped `server.json`, and the shipped `public/urlrewrite.xml` gives you pretty URLs out of the box, because CommandBox's underlying server uses the same Tuckey rewrite filter the wheels CLI does. |
| 50 | + |
| 51 | +Engine selection is the CommandBox you already know: |
| 52 | + |
| 53 | +```bash |
| 54 | +server start cfengine=lucee@7 |
| 55 | +server start cfengine=adobe@2023 |
| 56 | +``` |
| 57 | + |
| 58 | +(BoxLang rides the same `cfengine=` mechanism — check `box server start help` on your CommandBox version for the current slug.) |
| 59 | + |
| 60 | +This is genuinely a CommandBox *advantage*: the wheels CLI's dev server runs its bundled Lucee, while `cfengine=` gives you the exact engine and version production runs — Adobe shops especially should develop on the engine they deploy to. |
| 61 | + |
| 62 | +## Datasources, the cfconfig way |
| 63 | + |
| 64 | +The scaffolded `box.json` already lists `commandbox-cfconfig` in its dev dependencies, so the blessed path for per-project datasources is a `cfconfig` block in `server.json`: |
| 65 | + |
| 66 | +```json |
| 67 | +"cfconfig": { |
| 68 | + "datasources": { |
| 69 | + "myapp_dev": { |
| 70 | + "dbdriver": "MySQL", |
| 71 | + "host": "localhost", "port": 3306, |
| 72 | + "database": "myapp", |
| 73 | + "username": "wheels", "password": "${DB_PASSWORD}" |
| 74 | + } |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +CFConfig applies it at server start, on whichever engine you picked. Point `set(dataSourceName="myapp_dev")` in `config/settings.cfm` at it and you're wired. The [Database and Multiple Datasources guide](https://guides.wheels.dev/v4-0-0/basics/database-and-multiple-datasources/#registering-a-datasource) shows this shape next to the lucee.json and engine-admin variants. |
| 80 | + |
| 81 | +## Framework tasks without the wheels CLI |
| 82 | + |
| 83 | +Here's the map your team actually needs — every `wheels` command you'd see in the docs, translated to what a CommandBox-served app does instead: |
| 84 | + |
| 85 | +- **Reload** after config changes: `?reload=true&password=<your-reload-password>` on any URL. The password comes from `.env`, which the app reads itself. |
| 86 | +- **Run migrations**: the browser migrator GUI at `/wheels/migrator` in development, or `application.wheels.migrator.migrateToLatest()` programmatically in any environment — or `set(autoMigrateDatabase=true)` and migrations apply at startup. |
| 87 | +- **Write migrations**: they're plain CFCs in `app/migrator/migrations/` named `YYYYMMDDHHMMSS_description.cfc` — the generator only saves boilerplate. |
| 88 | +- **Run tests**: `/wheels/app/tests` in a browser; add `?format=json` for machine-readable results. This URL is literally what `wheels test` calls under the hood. |
| 89 | +- **Seed**: `application.wheels.seeder.runSeeds()` from any server-side code path. |
| 90 | +- **Generators**: no runtime equivalent — they just write files. The patterns live in [The Basics](https://guides.wheels.dev/v4-0-0/basics/), and honestly, most CommandBox teams we know have their own scaffolding habits anyway. |
| 91 | + |
| 92 | +Two fine points. First, the `/wheels/*` browser surfaces are development-only — a hard allowlist since 4.0.4, no setting opens them in production; the programmatic calls are the production path. Second, the `<datasource>_test` auto-swap during test runs is a wheels-CLI convenience — the raw runner uses whatever datasource the app is configured with, so point your dev datasource somewhere you're happy to let `tests/populate.cfm` rebuild. |
| 93 | + |
| 94 | +Every task page in the guides now shows this as a first-class labeled path — "Without the wheels CLI" — next to the CLI one. That's your column. |
| 95 | + |
| 96 | +## CI with the setup-commandbox action |
| 97 | + |
| 98 | +Ortus publishes a GitHub Action, and the whole test job is four steps: |
| 99 | + |
| 100 | +```yaml |
| 101 | +jobs: |
| 102 | + test: |
| 103 | + runs-on: ubuntu-latest |
| 104 | + env: |
| 105 | + WHEELS_CI: "true" |
| 106 | + steps: |
| 107 | + - uses: actions/checkout@v4 |
| 108 | + - uses: Ortus-Solutions/setup-commandbox@v2 |
| 109 | + - run: box install && box server start |
| 110 | + - run: | |
| 111 | + curl -s --max-time 600 -o results.json \ |
| 112 | + "http://localhost:8080/wheels/app/tests?format=json" |
| 113 | + python3 -c "import json; d=json.load(open('results.json')); \ |
| 114 | + raise SystemExit(0 if d.get('totalFail',0)==0 and d.get('totalError',0)==0 else 1)" |
| 115 | +``` |
| 116 | +
|
| 117 | +And because engine selection is a `server start` argument, a cross-engine matrix is just `strategy.matrix.cfengine: ["lucee@7", "adobe@2023", "adobe@2025"]` with `fail-fast: false`. The [CI Integration guide](https://guides.wheels.dev/v4-0-0/testing/ci-integration/) has the full version including browser-test gating and caching. |
| 118 | + |
| 119 | +## Packages: mind the two registries |
| 120 | + |
| 121 | +One naming collision to keep straight, because both registries matter to a CommandBox shop: |
| 122 | + |
| 123 | +- **ForgeBox** is where the *framework* lives — `wheels-base-template`, `wheels-core`. That's what `box install` talks to. |
| 124 | +- **The Wheels packages registry** (`wheels-dev/wheels-packages` on GitHub) is where *framework packages* live — `wheels-sentry`, `wheels-hotwire`, `wheels-basecoat`, `wheels-i18n`. That's what `wheels packages add` talks to. |
| 125 | + |
| 126 | +A package listed in one is not necessarily in the other. Without the wheels CLI, installing a package is: download its release, extract into `vendor/<name>/`, reload. The loader auto-discovers everything under `vendor/*/package.json` and doesn't care how the files arrived — `box install`, git submodule, unzip, rsync; all the same to it. |
| 127 | + |
| 128 | +## What you'd be giving up (an honest accounting) |
| 129 | + |
| 130 | +Staying CommandBox-only, the wheels CLI features you genuinely don't get are: the generators, `wheels console` (the REPL against your running app), `wheels upgrade check` (the breaking-change scanner — though you can run it once on a laptop without adopting anything on your servers), the `wheels deploy` deployer, and the MCP server for AI-assisted development. None gate the framework; all are reasons you might someday install the CLI *alongside* CommandBox — which works fine, since they don't fight over anything. |
| 131 | + |
| 132 | +The point of Wheels 4's tooling posture isn't that everyone should use the wheels CLI. It's that the framework runs identically no matter what installed it and what serves it — and that your team's decade of CommandBox muscle memory is an asset, not technical debt. Start at [Installing with CommandBox](https://guides.wheels.dev/v4-0-0/start-here/installing-with-commandbox/); the support-tier table there is the durable version of this post's contract. |
| 133 | + |
0 commit comments