The suprnova make:* family scaffolds the conventional file for each
piece of a project - a controller, an action, a middleware, a console
command, a domain error, a scheduled task, an Inertia page or props
struct, a database migration - and wires the new module into its
parent mod.rs (and where needed, src/lib.rs and cmd/main.rs).
Reach for them when you'd otherwise be retyping the same boilerplate
pub mod x;import line, which is most of the time.
make:controller
Scaffold a controller - a file in src/controllers/ with a single
#[handler] async fn named invoke.
The name is normalised to snake_case for the file name and used
as-is for the controller: echo in the response. Only ASCII letters,
digits, and _ are accepted - paths like api/User are rejected.
Generated file
// src/controllers/user.rs
use ;
pub async
What it wires
- Writes
src/controllers/<name>.rswith the#[handler]fn. - Adds
pub mod <name>;tosrc/controllers/mod.rs(creates the file if it didn't exist). - Prints a hint to add a route in
src/routes.rs:.get("/<name>", controllers::<name>::invoke).
See Controllers for the handler contract,
extractors, and the routes! macro.
make:action
Scaffold a single-responsibility action - a container-resolvable
struct with an async execute method that returns a
Result<String, FrameworkError> so the skeleton compiles before you
fill in the body.
The name is PascalCased; an Action suffix is appended if missing,
and the file is the snake-cased struct name.
Generated file
// src/actions/create_user_action.rs
use ;
What it wires
- Writes
src/actions/<snake>.rs. - Adds
pub mod <snake>;tosrc/actions/mod.rs. #[injectable]registers the action with the container at link time, so any controller can resolve it viaApp::get::<CreateUserAction>()and callaction.execute().await?.
See Actions for the resolve-and-invoke pattern and how actions compose with the container.
make:middleware
Scaffold a middleware - a unit struct that implements
suprnova::Middleware. The default body times the inner handler and
logs the inbound + outbound events with the per-request id, so it
runs end-to-end the first time.
The name is PascalCased; a Middleware suffix is appended if missing.
The file uses the snake-cased base name (without the suffix), e.g.
Auth → src/middleware/auth.rs, struct AuthMiddleware.
Generated file
// src/middleware/auth.rs
use Instant;
use ;
;
What it wires
- Writes
src/middleware/<snake>.rs. - Adds
mod <snake>;+pub use <snake>::<StructName>;tosrc/middleware/mod.rs(creates it if needed). - Prints both the per-route shape
(
.get("/path", handler).middleware(AuthMiddleware)) and the global shape (global_middleware!(middleware::AuthMiddleware)inbootstrap.rs).
See Middleware for the full chain semantics, ordering, and the global vs per-route distinction.
make:command
Scaffold a console command - a #[derive(clap::Parser, Command)]
struct that the per-project console binary picks up via inventory
at link time. The default body is a println!("…: not yet implemented") so the command runs immediately.
Naming follows three rules:
- Inputs containing
:are used verbatim as the registered command name (Laravel namespace style:db:seed,mail:send). - Otherwise the snake-cased fn name is kebabbed for the registered
name (
CleanCache→ commandclean-cache). - The Rust file and struct are always snake-cased / PascalCased forms of the same identifier.
Generated file
// src/commands/clean_cache.rs
use async_trait;
use Parser;
use ;
What it wires
- Writes
src/commands/<snake>.rs. - Adds
pub mod <snake>;tosrc/commands/mod.rs(creates it if needed). - Warns loudly if
src/lib.rsis missingpub mod commands;- the command won't link into the console binary without it. - Prints the run command:
cargo run --bin console -- clean-cache.
See Console for the full typed-command surface, the
#[command] shorthand for argv-only handlers, and the per-project
console binary's role.
make:error
Scaffold a domain error - a unit struct annotated with
#[domain_error] so it carries an HTTP status, a Display message,
and a From<…> for FrameworkError impl out of the box.
The name is PascalCased for the struct and snake-cased for the file. The default status is 500 and the message is the sentence-cased struct name - change both attributes in the generated file to match the situation.
Generated file
// src/errors/user_not_found.rs
use domain_error;
;
Change status = 500 to whatever fits - 404 for not-found,
402 for payment-required, 403 for forbidden - and edit the
message string. For richer payloads, add named fields to the struct
and reference them in the message via interpolation in a hand-rolled
Display impl (drop the #[domain_error] macro at that point).
What it wires
- Writes
src/errors/<snake>.rs. - Adds
pub mod <snake>;tosrc/errors/mod.rs(creates it if needed). - Warns about declaring
mod errors;insrc/lib.rsif theerrors/directory was created fresh.
Using it
Inside a handler returning Response, lift the domain type to a
FrameworkError so ? short-circuits cleanly:
use crateUserNotFound;
use FrameworkError;
pub async
The Errors chapter covers the full custom-error story,
including when to use #[domain_error] vs AppError::bad_request(…)
vs a hand-rolled HttpError impl.
make:task
Scaffold a scheduled task - a unit struct that implements
suprnova::Task and prints structured start/finish lines so the
scaffold logs progress before you fill in the real body.
The name is PascalCased; a Task suffix is appended if missing.
The file is the snake-cased struct name, e.g. CleanupLogs →
src/tasks/cleanup_logs_task.rs.
Generated file
// src/tasks/cleanup_logs_task.rs
use Instant;
use async_trait;
use ;
;
What it wires
The first make:task invocation does heavier wiring than the other
generators - it creates the scheduler's surface in the project from
scratch:
- Creates
src/tasks/andsrc/tasks/mod.rsif missing. - Creates
src/schedule.rs(theregister(schedule: &mut Schedule)entrypoint) if missing. - Declares
pub mod schedule;andpub mod tasks;insrc/lib.rs. - Inserts
.schedule(<crate>::schedule::register)into theApplication::new()chain incmd/main.rsorsrc/main.rs, immediately before.run(). - Writes
src/tasks/<snake>.rsand adds it tosrc/tasks/mod.rs.
Subsequent invocations skip the steps that already ran.
Registering the task
Open src/schedule.rs and add a registration call with the fluent
schedule API:
use Schedule;
use crateCleanupLogsTask;
Then run the scheduler:
See Scheduling for the full task surface (hourly,
weekly, cron(...), between, when, without_overlapping,
timezone handling) and CLI Scheduling for the
run-as-cron vs run-as-daemon trade.
make:inertia
Scaffold either an Inertia page component (default) or a typed Data
struct (--data), depending on the flag. The page generator detects
the frontend framework (Svelte 5, React 19, Vue 3.5) from .env and
emits the matching file extension.
Page mode (default)
The name is PascalCased and the suffix Page is appended if missing,
so About → AboutPage. The file lands in frontend/src/pages/
with the per-frontend extension: AboutPage.svelte for Svelte,
AboutPage.tsx for React, AboutPage.vue for Vue.
Example (Svelte):
<!-- frontend/src/pages/AboutPage.svelte -->
<div class="font-sans p-8 max-w-xl mx-auto">
<h1 class="text-3xl font-bold">AboutPage</h1>
<p class="mt-2">
Edit <code class="bg-gray-100 px-1 rounded">frontend/src/pages/AboutPage.svelte</code> to get started.
</p>
</div>
Render it from a controller:
inertia_response!
See Frontend Pages and Inertia Responses for the bridge between controllers and pages, partial reloads, and shared props.
Data struct mode (--data)
Emits a #[derive(Data, Validate)] struct in app/src/props/
(not src/props/ - the app/ prefix is hardcoded so the file lands
in the workspace's example/host app):
// app/src/props/user_props.rs
use Data;
use Validate;
Use it in a controller to validate request bodies:
let dto: UserProps = req.validate_json.await?;
make:migration
Scaffold a timestamped SeaORM migration file. Covered in detail in
CLI Migrations, which also walks the
migrate / migrate:rollback / migrate:status / migrate:fresh /
db:sync commands. The short form:
The migration name is preserved verbatim and prefixed with a
YYYYMMDDHHMMSS_ stamp so files sort chronologically. The generated
file lands in migrations/.
See Migrations for the schema-builder surface and
Database Testing for the TestDatabase::fresh
pattern that runs migrations against an isolated database per test.
generate-types
Emit TypeScript interfaces from every Rust struct annotated with
#[derive(InertiaProps)]. The dev server runs this automatically; the
standalone command is for CI checks and one-shot regenerations.
| Option | Default | Description |
|---|---|---|
-o, --output <PATH> |
frontend/src/types/inertia-props.ts |
Output file path |
-w, --watch |
off | Watch source files and regenerate on change |
# One-shot
# Watch mode (useful when you don't want to run the full dev server)
# Custom output path
A Rust shape on the left produces a TypeScript interface on the right:
export interface UserPageProps {
user: User;
posts: Post[];
}
See Frontend TypeScript Types for the full mapping table (enums, options, dates, nested structs) and the override hooks.
Why Suprnova diverges
Laravel's php artisan make:* drops a file in the right directory
and that's it - PSR-4 autoloading picks the new class up the next
time the framework boots. Rust has no equivalent. A file at
src/foo/bar.rs isn't compiled into the crate until src/foo/mod.rs
declares pub mod bar;, and the parent directory has to be wired up
the same way in src/lib.rs.
So every suprnova make:* generator does two things instead of one:
it writes the new file and edits the closest mod.rs (and, for
make:task and make:command, src/lib.rs and cmd/main.rs as
well). That's why every generator prints a Created src/.../mod.rs
or Updated src/.../mod.rs line - the wiring is part of the work,
not a follow-up step you remember on your own.
Summary
| Command | Creates | Wires into |
|---|---|---|
make:controller <name> |
src/controllers/<snake>.rs |
controllers/mod.rs |
make:action <Name> |
src/actions/<snake>_action.rs |
actions/mod.rs |
make:middleware <Name> |
src/middleware/<snake>.rs |
middleware/mod.rs |
make:command <name> |
src/commands/<snake>.rs |
commands/mod.rs (+ warns about lib.rs) |
make:error <Name> |
src/errors/<snake>.rs |
errors/mod.rs |
make:task <Name> |
src/tasks/<snake>_task.rs |
tasks/mod.rs, schedule.rs, lib.rs, main.rs |
make:inertia <Name> |
frontend/src/pages/<Name>Page.<ext> |
(no module wiring) |
make:inertia <Name> --data |
app/src/props/<snake>.rs |
(no module wiring) |
make:migration <name> |
migrations/YYYYMMDDHHMMSS_<name>.rs |
(no module wiring) |
generate-types |
frontend/src/types/inertia-props.ts |
n/a |
Next
- CLI Overview - the full subcommand table
- Console - the per-project console binary that
make:commandfeeds into - Controllers - the handler contract
make:controllerscaffolds - Scheduling - the fluent schedule API used to
register tasks generated by
make:task - CLI Migrations - the migrate / db:sync
commands that pair with
make:migration
