The xr CLI
xr is Larust’s artisan. Every command below also works as cargo run
-p larust-cli -- <command> from inside the workspace checkout, if you’d
rather not install it globally (see Installation).
- Project scaffolding
- Generators (
make:*) - Running your app
- Database
- Background work
- Deployment & lifecycle
- Converting an existing Laravel app
- Next
Project scaffolding
xr new [path] [--auth] [--tauri] [--features <list>] [--workspace <path>]
Create a new app. Omit path for an interactive wizard that asks for
everything below one question at a time instead.
| Flag | Meaning |
|---|---|
--auth |
Scaffold session-based auth (User model, register/login/logout, guarded routes) |
--tauri |
Also scaffold src-tauri/ (a native desktop build) - see Deployment & Desktop Apps |
--features <a,b,c> |
Enable optional larust-support features: cashier-stripe, db, passport, pennant, permissions, reverb, sanctum, scout, shield, sitemap, socialite - see Optional Features for what each one does |
--workspace <path> |
Resolve the framework’s crates as local path dependencies against a Larust checkout instead of the default version = "..." dependencies against the published crates - for a contributor scaffolding against local, possibly-unpublished framework changes |
Every Larust crate is published to crates.io, so by default xr new
needs no checkout at all - the generated Cargo.toml depends on the
published crates directly. --workspace <path> is the explicit opt-out,
not something auto-detected from where you happen to run the command.
xr add tauri
Retrofit Tauri desktop support onto an already-scaffolded app, run from
inside it - the only xr add target today (the other optional features
splice fixed text into main.rs/routes/*.rs only at generation time,
so retrofitting them safely onto an already-hand-edited file isn’t
attempted).
Generators (make:*)
| Command | Generates |
|---|---|
xr make:controller <Name> [--resource] |
A controller; --resource adds all 7 RESTful methods |
xr make:model <Name> [--migration] |
A model; --migration also writes a matching CREATE TABLE migration |
xr make:request <Name> |
A #[derive(FormRequest)] struct |
xr make:middleware <Name> |
A middleware function skeleton |
xr make:policy <Name> [--user <Type>] |
A Policy<U> impl skeleton (--user if your Authenticatable isn’t User) |
xr make:factory <Name> |
A Factory impl skeleton for <Name> in database/factories/ - see Testing |
xr make:migration <name> |
An empty, correctly-numbered migration file |
xr make:command <Name> |
A named CLI command (larust_support::console::Command) - see Named CLI Commands |
Running your app
xr dev [--port <port>] [--debug]
Watches your app, rebuilds and hot-swaps it on save with zero dropped requests, and pushes a live-reload signal to any open browser tab. Binds the port immediately (serving a build-status page until the first build finishes), and leaves the last known-good build running if a later one fails. See Your First App.
--debug turns on maximum-verbosity mode for that one session, without
editing .env: APP_DEBUG=true (full error detail pages instead of the
generic 404/500), LOG_LEVEL=trace (the most verbose logging tier this
framework supports), and RUST_BACKTRACE=full (full panic backtraces).
Same “explicit flag overrides .env for this run only” precedent
--port already sets for APP_PORT. Never use it for a real deploy -
see Error Handling’s own
APP_DEBUG warning.
On Windows, a rebuild can occasionally fail with failed to remove file
...\target\debug\<name>.exe / Access is denied even though nothing in
xr dev’s own process tree holds that file open - almost always a brief
antivirus scan of the freshly-linked binary. xr dev already retries a
few times automatically before reporting failure, so this is usually
invisible; see docs/GOTCHAS.md’s entry on it for the full story (shared
root cause with xr upgrade’s identical failure, and that command’s
--fix flag).
xr build [--fresh]
Builds (or rebuilds) an app’s frontend assets standalone (npm run
build, the same step xr deploy runs before publishing) - a no-op if
the app has no node_modules/. --fresh clears Vite’s dependency cache
and public/build/ first, for when a stuck build needs a clean slate.
xr route:list
Prints every registered route (method, path, name).
xr command:list
Prints every registered named command’s name and description.
xr <your-command-name>
Any name that does not match one of the fixed subcommands is forwarded to
your routes/console.rs::commands() registry. For example:
xr report:posts --format=json
A name matching nothing at all is a loud error, not a silent fall-through into starting the web server.
xr restart
Asks an already-running app to perform a zero-downtime restart handoff - a new process takes over the listening socket before the old one starts draining, so in-flight requests finish and no new connection is ever refused.
xr list
Lists every xr dev session currently running on this machine - PID, app
name, port, uptime, and directory. Useful once you’ve got more than one
app’s xr dev going at the same time and need to tell them apart.
xr kill [--id <pid>] [--all]
Stops the xr dev session tied to the current directory: sends the
currently-running server the same graceful STOP signal xr dev’s own
Ctrl+C handler uses, then ends the xr dev watcher process itself. With
--id <pid> (the id xr list prints), stops a specific session instead,
regardless of which directory you run it from.
--all stops every other running xr process on the machine outright -
not just ones xr list knows about. xr list/--id only ever find a
session that successfully registered itself at startup; something else
entirely (a different terminal’s still-open xr dev, a hung xr deploy
--run, any other stray xr invocation) can still be holding the
installed xr binary open with nothing in xr list to explain it -
exactly the situation that leaves xr upgrade failing with a confusing
“Access is denied” on Windows and no obvious way to resolve it. --all
finds those by executable name directly from the OS process list instead
of relying on that registry, gracefully stopping any served app it
recognizes along the way and forcefully terminating everything else.
Database
xr migrate
Runs every pending migration in database/migrations/, in order.
xr migrate:fresh [--seed]
Drops every table (except the framework’s own sessions table) and
reapplies every migration from scratch - the honest alternative to a
rollback this framework can’t actually offer (migrations have no
down()). See Migrations & the Query Builder.
--seed also runs database/seeders afterward.
xr db:seed
Runs database/seeders::run() against the current database - see
Testing.
xr db:list / xr db:get <key> / xr db:put <key> <value> / xr db:forget <key>
Manage the embedded key-value store from the command line (requires the
db optional feature). db:put’s value is parsed as JSON when possible
(numbers, booleans, quoted strings), otherwise stored as a plain string.
Background work
xr queue:work
Starts a worker that claims and processes queued jobs until stopped. See Events, Queues, Scheduling & Commands.
xr schedule:work
Runs due scheduled tasks once a second until stopped.
Not safe to run as more than one process against the same app, unless a
task explicitly opts into .on_one_server(). See Task
Scheduling.
Deployment & lifecycle
xr optimize
Builds production artifacts without publishing or restarting an app: frontend
assets when the app has a node_modules/ pipeline, then Cargo’s optimized
release binary. Larust compiles routes, templates, and framework code ahead of
time, so there is no mutable runtime code cache to generate; this is the safe
Rust equivalent of Laravel’s optimization prebuild. Use it in CI or before a
deploy, then run xr deploy when you are ready to publish.
xr deploy [--run] [--service] [--clean | --clean-all]
Builds and publishes a release according to DEPLOY_TYPE (.env, "web"
by default): cargo build --release, publish to storage/releases/,
then the same zero-downtime restart handoff xr restart uses against
whatever’s already running. DEPLOY_TYPE=app instead runs cargo tauri
build from src-tauri/. --run cold-starts the freshly published
release in the background if nothing is currently running to hand off to
(the very first deploy) - no effect otherwise. --service does the same,
but via xr service:install (a real systemd unit) instead of a bare
background process - crash- and reboot-safe from the first deploy, no
separate step needed; wins over --run if both are given.
--clean runs cargo clean --release only after a successful web deployment,
preserving published releases and development caches. --clean-all runs
plain cargo clean instead, removing debug dependencies and all other Cargo
target artifacts too. Choose one flag. The next deploy rebuilds dependencies;
--clean-all also forces development rebuilds. Neither reduces peak build
space or clears node_modules//Cargo downloads. Shared target directories
share cleanup too. Desktop deployments reject both flags to preserve bundles.
Cleanup errors report failure without rolling back the published release. See
Deployment & Desktop Apps.
xr run
Starts the currently-published release (storage/releases/current,
written by xr deploy) if nothing is already listening on the app’s own
port - a no-op, not an error, if something already is. For an xr deploy
run without --run/--service (publishing and starting as separate,
independently-auditable steps), or for starting a release back up after
it was stopped (xr kill, a crash with no xr service:install in place)
with no rebuild or republish involved. Errors clearly if nothing has been
published yet (“run xr deploy first”).
xr service:install / xr service:uninstall
Registers (or unregisters) the app as a systemd service - Linux only -
so it restarts on an actual crash and starts automatically on boot,
closing the gap xr deploy --run alone leaves open (it starts the app
once; nothing ties it to a reboot). Run from the app’s own directory, on
the machine serving it, after at least one xr deploy - or skip this as
its own step and just pass --service to xr deploy directly. See
Deployment &
Desktop Apps
for why this uses Restart=on-failure, not Restart=always.
xr service:install --simple <binary path> registers a minimal
deployment instead - just a compiled binary and an .env file, with no
xr deploy-managed storage/releases/current to point at. Run it from
wherever the binary and .env actually live; see Deployment & Desktop
Apps
for what it generates and why it also sets LARUST_APP_ROOT in the
unit file.
xr upgrade [--force] [--legacy] [--stop-running] [--fix]
Upgrades the xr binary itself (not your app - see xr update for
that). By default, reinstalls larust-cli from crates.io (cargo
install larust-cli) - matching an xr you got via cargo install
larust-cli in the first place. --force reinstalls unconditionally
(cargo install larust-cli --force), even if you’re already on the
latest version; without it, cargo install already upgrades on its own
whenever a newer version exists, and no-ops otherwise.
If another running xr process is holding the installed binary open,
cargo install’s own last step (replacing that binary) fails with
“Access is denied” on Windows - even when xr list shows nothing
running, since that only ever tracks xr dev sessions that registered
themselves successfully, not every xr invocation. xr upgrade checks
for this upfront and refuses with a clear message naming the pids
involved; pass --stop-running to have it stop them itself first (the
same mechanism as xr kill --all) and then proceed.
That same “Access is denied” can also show up with no other xr
process involved at all - most often Windows Defender (or another
antivirus/EDR product) briefly locking the freshly-built .exe while it
scans it. xr upgrade already retries automatically a few times before
giving up. If it still fails, --fix excludes .cargo\bin from
Windows Defender’s real-time scanning (needs an elevated/Administrator
shell - if xr isn’t already running as one, it prints the exact
Add-MpPreference command to run yourself) and retries once more.
Windows-only; a no-op anywhere else.
--legacy switches to the original mechanism instead, for an xr built
from a real Larust checkout (install.sh/install.ps1/cargo install
--path): fetches, fast-forwards the current branch only if there are
new upstream commits, then reinstalls from that checkout. Does nothing
if already up to date. --force in this mode skips the freshness check
and just reinstalls from the checkout’s current state - the “repair”
equivalent of re-running install.sh/install.ps1.
“The checkout it was built from” (used only in --legacy mode) is baked
into the binary at cargo install --path time, which won’t be right
forever - a moved checkout, an xr binary shared across machines, or a
deployment whose layout never matches wherever it happened to get
built. Set LARUST_CHECKOUT_ROOT (a real environment variable, or a
line in a .env file in whatever directory you run xr upgrade
--legacy from) to point it somewhere else without rebuilding xr
itself.
xr update
Updates the current app’s Cargo dependencies within their declared
version constraints (composer update’s equivalent) - not xr itself.
xr release [--major|--minor|--patch]
Prepares a release of the current app - a thin front-end over plain
git, the same shape xr audit/xr update already use over cargo
audit/cargo update. Refuses to run with an unclean working tree.
With a bump flag, rewrites Cargo.toml’s [package] version (X.Y.Z
only - no pre-release/build-metadata handling) and commits that change
as chore: release vX.Y.Z; with no flag, tags whatever version is
already there. Builds release notes from every commit since the last
tag (grouped by this repo’s own commit-type convention - feat/fix/
refactor/perf/docs/test/ci/chore; anything else lands under
“Other” rather than being dropped), then creates a local, annotated
vX.Y.Z tag with those notes as its message.
Never pushes anything, and never deploys anything. Prints the exact
git push && git push origin vX.Y.Z to run yourself when ready, plus a
reminder that xr deploy on the server is still the separate next step
- this command only prepares the tag. Wiring a server to deploy
automatically on push (a bare git remote +
post-receivehook) is a separate, already-well-understood pattern this doesn’t attempt to reinvent - see Deployment & Desktop Apps.
xr audit
Runs cargo-audit over the resolved workspace lockfile, checking for
known security advisories.
Converting an existing Laravel app
xr convert <path> --out <dir> [--workspace <path>]
Converts a whole Laravel app into a fresh, empty <dir> - refuses to run
if <dir> already exists and isn’t empty (no incremental/merge support;
re-running on an already-converted, hand-edited project needs a new
directory). See Converting a Laravel App.
Like xr new, the generated app depends on the published crates.io
crates by default - --workspace <path> is the same contributor-only
opt-out, resolving local path dependencies against a Larust checkout
instead.
xr convert --file <blade-path> --destination <xr-path>
Re-converts a single .blade.php template in isolation (overwriting
<xr-path> if it exists) - for pulling one template through a converter
fix without redoing the whole project.
Next
Testing covers TestClient and the rest of larust-testing.