Command Line Tools
Upgrade
wheels upgrade has two verbs. wheels upgrade check is a read-only scanner — it compares the framework version installed in vendor/wheels/ against a target release, then greps the project for patterns that are known to break between major versions. wheels upgrade apply performs the actual framework swap, replacing vendor/wheels/ with the copy bundled inside the installed CLI. Neither verb edits box.json, rewrites your code, or installs packages.
You’ll use this for:
- Previewing a major-version bump (2 → 3, 3 → 4) before you pull the trigger —
check. - Confirming an upgrade is “clean” — same major version, no code changes expected —
check. - Pinning the scan to a specific target with
--to=<version>when you’re not chasing the latest release —check. - Swapping
vendor/wheels/from the CLI bundle without a manual zip dance —apply.
Synopsis
Section titled “Synopsis”wheels upgrade check [--to=<version>] [--format=json] [--strict]wheels upgrade apply [--to=<version>] [--nobackup]Calling wheels upgrade with no subcommand prints concise usage listing both verbs and exits 0. Any other subcommand — a typo like wheels upgrade chekc — prints the usage and then hard-errors with a non-zero exit, so a misspelled verb in a script fails loudly instead of looking like a successful run.
What it does
Section titled “What it does”wheels upgrade check performs four steps, in order:
- Reads the current version from
vendor/wheels/box.json. If the file is missing or malformed, the version is reported asunknownand the comparison below is skipped. - Resolves the target version. If you pass
--to=<version>, that wins. Otherwise the command hits the GitHub releases API (api.github.com/repos/wheels-dev/wheels/releases/latest) and strips the leadingvfrom the tag name. If the network call fails, the command prints a yellow notice and exits — it will not invent a target. - Compares major versions. If current and target share the same major, the command notes “Same major version — no known breaking changes” for the major-transition checks, then still runs its advisory scan for the same-major patterns below.
- Runs breaking-change checks. Each check is either a directory existence test or a regex grep across a sub-tree of the project. Major-version transitions add their own checks on top of the advisory scan. Hits are printed as issues (yellow); absences are printed as passed checks (green).
Exit status: when breaking findings exist the command throws Wheels.UpgradeCheckFailed after the report flushes, exiting non-zero so it can gate CI. Without --strict, advisory findings never affect the exit code. With --strict, advisory findings escalate to the same hard-fail path — useful for CI pipelines that want to gate on opt-in convention changes, not just breaking ones. With --format=json the human report is replaced by a single JSON document (currentVersion, targetVersion, success, strict, breaking, advisories, passed, guide) — the non-zero exit on breaking or strict-mode advisory findings still applies. The document’s success field tracks the exit code precisely: it is false whenever the process exits non-zero, including the --strict + advisory-only case, and the strict field is echoed back so consumers can tell success: false with empty breaking[] apart from a data inconsistency.
Nothing in the project is modified. No files are written. The command does not stage, commit, or touch git state.
Prerequisites
Section titled “Prerequisites”None that the command enforces — but in practice:
- Commit first. The scanner is read-only, but the actual upgrade —
wheels upgrade apply(or a manualvendor/wheels/drop-in if you vendor by hand) — replaces framework code. Have a clean working tree so you can diff and roll back. (brew upgrade wheelsonly updates the CLI binary, never your app’s vendored framework copy.) - Run your tests. Rerun the test suite after the framework swap, not after this command —
wheels upgrade checkdoes not exercise anything, it only greps. - Internet access is required when you don’t pass
--to=. The command fetches the latest release tag from GitHub.
Flags — check
Section titled “Flags — check”| Flag | Description |
|---|---|
--to=<version> | Target version to scan against (e.g. --to=4.0.0). When omitted, the command queries GitHub for the latest release tag. If the GitHub call fails and no --to= is given, the command aborts. |
--format=json | Emit a single machine-readable JSON report instead of the human output — for CI pipelines. Breaking findings (and advisory findings when --strict is set) still exit non-zero. |
--strict | Escalate advisory findings (the “Recommended Improvements” section) to the same hard-fail path as breaking findings. The command throws Wheels.UpgradeCheckFailed and exits non-zero so CI can gate on opt-in convention changes. Without this flag, advisories are reported but never fail the check. Mirrors Django’s --fail-level WARNING / Mix’s --warnings-as-errors. |
Example
Section titled “Example”wheels upgrade check --to=4.0.0Sample output (trimmed):
Current version: 3.5.1Target version: 4.0.0
Breaking Changes (2 found): ! Legacy plugin directory (deprecated in 4.x) plugins/ -> Migrate to packages/ + vendor/ system
! Old test base class (wheels.Test) tests/specs/models/UserSpec.cfc:1 -> Change to extends="wheels.WheelsTest"
All Clear (1 checks): + Direct WireBox references
Apply with: wheels upgrade applyWhen current and target share a major version, the major-transition note is shown but the advisory scan still runs:
Current version: 4.0.0Target version: 4.0.1
Same major version — no known breaking changes.Scanning for opt-in recommendations...
Apply with: wheels upgrade applyWhat gets checked
Section titled “What gets checked”The checks are hard-coded in the CLI and keyed by the major-version transition:
2.x → 3.x
- Existence of
plugins/at the project root (legacy plugin directory; skipped on a 2.x → 4.x jump where the 4.x check below covers it). extends="wheels.Test"orextends="wheels.Testbox"(either quote style) anywhere undertests/(old test base classes).
3.x → 4.x
- Existence of
plugins/at the project root (deprecated plugin location). extends="wheels.Test"orextends="wheels.Testbox"(either quote style) anywhere undertests/.application.wireboxorwirebox.system.iocreferences underapp/,config/, and the rootApplication.cfc— these must move toservice()/application.wheelsdi, andnew wirebox.system.ioc.Injector(...)bootstraps tonew wheels.Injector()(#1888).renderPage()/renderPageToString()underapp/— removed in 4.0; replace withrenderView()/renderView(returnAs="string"), or installwheels-legacy-adapterfor a soft landing.new wheels.middleware.Cors()withoutallowOriginsinconfig/— the 4.0 default changed from wildcard to deny-all (#2039).new wheels.middleware.RateLimiterinconfig/— prompts the user to verifytrustProxyandproxyStrategy, whose defaults hardened in 4.0 (#2024, #2088).allowEnvironmentSwitchViaUrl=trueinconfig/— the production default flipped tofalse(#2076).- Missing
csrfCookieEncryptionSecretKeyinconfig/— when absent the key auto-generates and CSRF cookies rotate on every deploy (#2054). - Legacy
wheels snippetsinMakefile,package.json,.github/workflows/, and top-level*.shfiles — renamed towheels generate snippets(#1852). - Existence of
tests/specs/functions/— renamed totests/specs/functional/(#1872). viteScriptTag,viteStyleTag, orvitePreloadTaginapp/views/—viteStrictManifestnow defaults totruein production, throwing on missing manifest entries (#2133).- (advisory)
new wheels.middleware.SecurityHeadersinconfig/— HSTS defaults on in production in 4.0; passhsts=falseif your load balancer already sets it (#2081). - (advisory)
protectsFromForgeryunderapp/— the CSRF cookie now setsSameSite; cross-site POSTs from third-party frames will break (#2035).
Each grep scan covers the file types relevant to that check (.cfc and .cfm for config and view checks; Makefile, package.json, .yml, .yaml, and .sh for the wheels snippets check) and reports path:lineNumber for every hit. Directory checks report only the top-level path when the directory exists and is non-empty.
wheels upgrade apply
Section titled “wheels upgrade apply”wheels upgrade apply replaces the app’s vendor/wheels/ with the copy of the framework bundled inside the installed CLI binary. Before any file is touched, the command announces the plan — printing the reserved backup path and the exact one-liner to recover if the swap is interrupted:
Backing up vendor/wheels -> vendor/wheels.bak-20260611-141502If this is interrupted, restore with: rm -rf "/path/to/app/vendor/wheels" && mv "/path/to/app/vendor/wheels.bak-20260611-141502" "/path/to/app/vendor/wheels"After the swap, it reports the version transition, the backup location, and the recovery one-liner:
Framework upgraded: 3.5.1 -> 4.0.2Backup: /path/to/app/vendor/wheels.bak-20260611-141502Recover with: rm -rf "/path/to/app/vendor/wheels" && mv "/path/to/app/vendor/wheels.bak-20260611-141502" "/path/to/app/vendor/wheels"If a safety check refuses the swap, the command prints only the refusal and exits non-zero — no backup is made and no restore command is shown, because there is nothing to restore.
Safety checks run before any mutation:
- Source (CLI-bundled) and target (
vendor/wheels/) must each sniff as a valid Wheels framework directory — a genericbox.jsonis not sufficient. - The command refuses to run inside the Wheels repo checkout itself (source = target).
- The command refuses outside a Wheels app (no
vendor/wheels/).
Flags — apply
Section titled “Flags — apply”| Flag | Description |
|---|---|
--to=<version> | Assert that the CLI’s bundled framework is exactly this version. Errors if the bundled version does not match — --to on apply is a safety assertion, not a download trigger. Use wheels upgrade check --to=<version> to scan before applying. |
--nobackup | Skip the backup. The old vendor/wheels/ is deleted before the new copy is placed. Useful when disk space is a concern or you have your own rollback strategy (git). |
Example
Section titled “Example”wheels upgrade check # scan for breaking changes firstwheels upgrade apply # swap vendor/wheels/ with automatic backupwheels upgrade apply --nobackupWhat gets updated
Section titled “What gets updated”check verb: Nothing — the scanner is read-only.
apply verb: vendor/wheels/ is replaced with the framework bundled in the installed CLI. By default, the old copy is moved to a timestamped vendor/wheels.bak-* directory before the swap, so recovery is a single mv.
To also update the CLI binary itself:
- Homebrew:
brew upgrade wheels - Scoop:
scoop update wheels
After each CLI upgrade, run wheels upgrade apply to update your app’s vendored framework copy.
Rollback
Section titled “Rollback”check verb: Because the command writes no files, there is nothing to roll back.
apply verb: The old vendor/wheels/ is moved to a timestamped backup before the swap. The pre-swap announcement prints the exact recovery command — copy it before the swap completes if you want it on hand:
rm -rf "/path/to/app/vendor/wheels" && mv "/path/to/app/vendor/wheels.bak-20260611-141502" "/path/to/app/vendor/wheels"If you passed --nobackup, recover from git instead:
git restore vendor/wheels/git clean -fd vendor/wheels/Or revert the commit that recorded the new vendor/wheels/ tree. The CLI binary itself is managed by your package manager — brew keeps the previous cellar around for brew switch-style rollback.