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, a checked view, an Inertia
page or props struct, a database migration, and the twenty-one kinds of
one-file generators Laravel's artisan has, from
a factory to an interface - 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. Its handle passes the request to next and
returns the response, as Laravel's middleware stub does, so the new
middleware changes nothing until you give it logic.
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.
A nested name writes under a subdirectory, as Laravel's
make:middleware Admin/EnsureRole writes under
app/Http/Middleware/Admin: Admin/EnsureRole (or Admin\EnsureRole)
writes src/middleware/admin/ensure_role.rs, struct
EnsureRoleMiddleware, reached as
crate::middleware::admin::EnsureRoleMiddleware.
Generated file
// src/middleware/auth.rs
use ;
/// Auth middleware.
;
With --test
--test also writes tests/<path>_middleware.rs
(tests/admin_ensure_role_middleware.rs for Admin/EnsureRole). The test
puts the middleware in front of a route, sends a request through
TestClient, and asserts the route's response comes back. It imports
the middleware from your library crate, so run the command from the
project root, where Cargo.toml and src/lib.rs are:
// tests/admin_ensure_role_middleware.rs
use EnsureRoleMiddleware;
use TestClient;
use ;
async
async
When you give the middleware logic, change the test to match it. See HTTP Tests for the client.
What it wires
- Writes
src/middleware/<snake>.rs, or for a nested name the same file undersrc/middleware/<dir>/. An existing file is never overwritten. - Adds
mod <snake>;+pub use <snake>::<StructName>;to the module file of that directory (src/middleware/mod.rs, orsrc/middleware/admin/mod.rs), creating it if needed. - For a nested name, declares each directory in the one above it:
pub mod admin;insrc/middleware/mod.rs. - With
--test, writes the test undertests/. - Prints both the per-route shape
(
.get("/path", handler).middleware(AuthMiddleware)) and the global shape (global_middleware!(crate::middleware::AuthMiddleware)inbootstrap.rs).
Every file is checked before anything is written, and the command writes all of them or none.
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 prints a line with
suprnova::console::line that says the command is not implemented, so the
command runs immediately and a test can read what it prints.
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.
live:make
Scaffold a Live component: a server-owned island whose typed actions arrive over the Live protocol and whose re-rendered view is morphed in place by the shipped browser runtime.
Names must be plain ASCII identifiers in any of Counter, TodoList,
todo-list, or todo_list form; the file and module are snake-cased,
the struct is PascalCased, and the registered component name is
<package>.<kebab> (for a package named demo-app: demo-app.counter).
Rust keywords, separators, dots, and non-ASCII input are rejected before
anything is written.
Generated file
// src/live/counter.rs
use ;
/// A counter island rendered by `live/counter.html`.
<!-- templates/live/counter.html -->
Count: {{ count }}
Increment
What it wires
- Validates every target path first and refuses traversal and symlinks; if the component file or the view already exists, it warns and writes nothing at all.
- Writes
src/live/<snake>.rsandtemplates/live/<snake>.htmlatomically; if any write fails, every file the run created or changed is rolled back, and any file that could not be restored is named in the error instead of being reported as untouched. - Inserts
pub mod <snake>;and.register::<snake::Pascal>()?into theregistry()builder insrc/live/mod.rs. Every project created bysuprnova newships that module with an empty registry, aroutes()function that installs the guarded reserved Live routes, and a bootstrap that binds the registry; an older project gets the same module created on first use. - Adds
pub mod live;tosrc/lib.rswhen it is missing. - Prints the bootstrap line that binds the registry, then the check
command:
suprnova live:check.
In a project that predates the Live module, bind the registry during
bootstrap and install the routes from cmd/main.rs by hand:
singleton;
.try_routes
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:
A task is a type that implements Task, whose only method is handle. The
name of a task and the times it runs at are set where you register it, not on
the trait. The scaffolded src/tasks/mod.rs carries a doc comment that shows
the same pair: a Task in src/tasks/ and its registration in
src/schedule.rs.
See Scheduling for the full task surface (hourly,
weekly, cron(...), without_overlapping, on_one_server,
timezone handling) and CLI Scheduling for the
run-as-cron vs run-as-daemon trade.
make:view
Scaffold a checked view, Laravel's make:view: an Askama template under
templates/ and a #[suprnova::view] struct that names it. The macro
compiles the template against the struct, so a name the template reads
that the struct lacks fails the build.
admin.dashboard and admin/dashboard name the same view, as in
Laravel: the template is templates/admin/dashboard.html, and the struct
is DashboardView in src/views/admin/dashboard.rs, reached as
crate::views::admin::dashboard::DashboardView. Each segment is letters,
digits, _ and -, starting with a letter; the template path keeps the
segment as you write it, and the module name is its snake case
(reports.user-summary → templates/reports/user-summary.html,
src/views/reports/user_summary.rs).
Generated files
<!-- templates/admin/dashboard.html -->
{{ title }}
// src/views/admin/dashboard.rs
/// The data `templates/admin/dashboard.html` renders.
Render it from a handler with ViewTemplate::render_view, and return the
HTML:
use ViewTemplate;
use ;
use crateDashboardView;
pub async
With --test
--test also writes tests/<path>_view.rs
(tests/admin_dashboard_view.rs), which renders the view with a title
and asserts the title is in the HTML. It imports the view from your
library crate, so run the command from the project root.
What it wires
- Refuses to overwrite: if the template, the view file or (with
--test) the test exists, the command says which and writes nothing. Pass--forceto overwrite them. - Writes
templates/<path>.htmlandsrc/views/<path>.rs. - Declares the modules on the way:
pub mod views;insrc/lib.rs,pub mod admin;insrc/views/mod.rsandpub mod dashboard;insrc/views/admin/mod.rs, creating each module file it needs. A declaration that is already there is left alone. - With
--test, writes the test undertests/.
Every file is checked before anything is written, and the command writes all of them or none.
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.
A nested name writes under the same directory of frontend/src/pages/,
and the component is named with it: Admin/Users writes
frontend/src/pages/Admin/UsersPage.svelte, rendered as
"Admin/UsersPage". Directory segments keep the spelling you give them.
An existing page is kept: the command says so and writes nothing. Pass
--force to overwrite it.
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!
--test also writes tests/<path>.rs (tests/admin_users_page.rs for
Admin/Users). The test answers a request with the page through
TestClient and asserts that the response names the component and that
its file exists under frontend/src/pages:
// tests/admin_users_page.rs
use TestClient;
use ;
async
async
--force overwrites the test too. --test and --data cannot be
combined.
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 src/props/, and warns
when it creates the directory, since src/lib.rs has to declare
pub mod props; for the struct to be reachable:
// 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/.
The generated file uses SeaORM's builder. The
schema builder is the alternative you write
by hand. See Migrations for both forms and
Database Testing for the TestDatabase::fresh
pattern that runs migrations against an isolated database per test.
The one-file generators
Twenty-one generators write one file from a stub, as Laravel's artisan
commands of the same names do: make:factory, make:seeder, make:cast,
make:channel, make:class, make:enum, make:event, make:listener,
make:job, make:job-middleware, make:mail, make:notification,
make:observer, make:policy, make:provider, make:resource,
make:rule, make:scope, make:test, make:trait and make:interface.
They share these rules:
- The name is a Rust identifier: ASCII letters, digits and
_, starting with a letter. A name whose module would be a Rust keyword (Typewould be the moduletype) is refused too. A refused name exits with status 1 and a message that says why, and nothing is written. A name in snake case is read as words (order_shippedisOrderShipped); any other keeps its spelling, with the first letter upper-cased. - One file,
src/<directory>/<snake>.rs, in the directory each section below names.make:classwritessrc/<snake>.rs, andmake:testwritestests/<snake>.rs. - The module is declared:
pub mod <directory>;insrc/lib.rs, andpub mod <snake>;in the directory'smod.rs, which the generator creates when the project has none. A declaration that is already there is left alone.make:testdeclares nothing, because every file undertests/is a crate of its own. - An existing file is kept. The command says the file exists, writes
nothing and exits 0. Pass
--forceto write the stub over it. - The path is printed:
Created <path>orUpdated <path>for each file the run writes, then the line that wires the new item in. - No clash with the stub's imports or Rust's prelude.
make:rule Ruleis refused, because the stub imports theRuletrait under that name, andmake:mail Optionis refused, because a type namedOptionwould shadow theOption<String>the stub's own signatures use. The same goes for a--modelor--eventof such a name.
Every file is checked before anything is written, and the command writes
all of them or none. Each stub compiles in a project suprnova new
creates.
✓ Created src/events/order_shipped.rs
✓ Updated src/lib.rs
✓ Created src/events/mod.rs
make:factory
Writes src/factories/<snake>.rs, a model factory:
a marker struct and its Factory impl. A Factory suffix is appended when
the name lacks one, and the model is the name without it: make:factory Post writes PostFactory for crate::models::post::Post. --model <Model>
names another model. When src/models/<snake>.rs is missing, the command
warns, since the project does not compile until the model exists.
// src/factories/post_factory.rs
use Factory;
use cratePost;
;
Set the fields a new model needs in definition, with a suprnova::Faker
value where one should vary. Every #[suprnova::model] struct implements
Default, so a field you leave out keeps its default.
make:seeder
Writes src/seeders/<snake>.rs, a seeder that db:seed runs.
A Seeder suffix is appended when the name lacks one.
// src/seeders/users_seeder.rs
use ;
;
Register it in src/bootstrap.rs with
suprnova::seed::register::<crate::seeders::users_seeder::UsersSeeder>();,
then run it with suprnova db:seed UsersSeeder.
make:cast
Writes src/casts/<snake>.rs, a custom attribute
cast: a unit struct that implements Cast. The stub
stores the value as it is, as Laravel's cast stub returns $value; change
Runtime, Storage and the two conversions to the types you need.
// src/casts/as_money.rs
use ;
;
Name it on a model field:
#[model(casts = { amount = crate::casts::as_money::AsMoney })].
make:channel
Writes src/broadcasting/<snake>.rs, a private
broadcast channel and who may join it. The
channel name is the type's words without Channel, joined by dots:
OrderUpdates is order.updates. Its authorize denies every subscriber
until you write the rule, as Laravel's channel stub does; req.auth_user_id()
names the user the session authenticated on the WebSocket upgrade.
// src/broadcasting/order_updates.rs
use async_trait;
use ;
use Request;
use Value;
;
Register it in the ChannelRegistry your bootstrap binds:
registry.register(OrderUpdates);.
make:class
Writes src/<snake>.rs and declares it in src/lib.rs, as Laravel's
make:class writes under app/: a plain struct with a doc comment and an
impl block. The names Lib and Main are refused, because they would
write the crate's own root.
// src/invoice.rs
/// A plain Rust type: give `Invoice` the fields and methods it needs.
;
make:enum
Writes src/enums/<snake>.rs, an enum with no variants yet, as Laravel's
enum stub has no cases. Write one variant per case.
// src/enums/status.rs
/// The values a `Status` can take: write one variant per case.
make:event
Writes src/events/<snake>.rs, an event: a struct whose fields
every listener receives, and its Event impl.
// src/events/order_shipped.rs
use Event;
Dispatch it with EventFacade::dispatch(OrderShipped {}).await?.
make:listener
Writes src/listeners/<snake>.rs, an event listener. With
--event <Event>, it handles that event, imported from
crate::events::<snake>; the command warns when that file is missing.
Without --event, it handles any event it is registered for, as Laravel's
listener stub takes object $event.
// src/listeners/send_shipment_notification.rs
use ;
use crateOrderShipped;
;
The listener of any event implements Listener<E> for every E: Event.
Register a listener in src/bootstrap.rs:
.await;
make:job
Writes src/jobs/<snake>.rs, a queued job: a struct whose
fields go into the queue payload, and its Job impl.
// src/jobs/process_podcast.rs
use ;
use ;
Register it at boot with
suprnova::queue::worker::register_job::<ProcessPodcast>(); and push it
with Queue::push(ProcessPodcast {}).await?.
make:job-middleware
Writes src/jobs/middleware/<snake>.rs, as Laravel writes under
app/Jobs/Middleware: job middleware that passes
the job on. It declares pub mod jobs; in src/lib.rs and
pub mod middleware; in the jobs module when they are missing.
// src/jobs/middleware/rate_limited_by_user.rs
use ;
;
Return it from a job's middleware(): vec![Arc::new(RateLimitedByUser)].
make:mail
Writes src/mail/<snake>.rs, a mailable. The subject is the
type's words, as Laravel's mail stub uses the class name's headline, and the
bodies are Tera templates whose context is the struct's fields.
// src/mail/order_shipped.rs
use ;
use async_trait;
use Mailable;
Send it with Mail::to("user@example.com").send(OrderShipped {}).await?.
make:notification
Writes src/notifications/<snake>.rs, a notification
sent on the mail channel, with the mail rendering
#[derive(NotificationMailable)] builds from its #[mail(...)] attribute.
// src/notifications/invoice_paid.rs
use ;
use serde_json;
use ;
Register its mail renderer at boot with
suprnova::register_mail_renderer::<InvoicePaid>()?;, then send it with
Notify::send(&user, &InvoicePaid {}).await?.
make:observer
Writes src/observers/<snake>.rs, a model observer
with the created, updated, deleted, restored and force_deleted
hooks Laravel's observer stub has. An Observer suffix is appended when the
name lacks one, and the model is the name without it; --model <Model>
names another. #[suprnova::observer] installs it when the application
boots. The attribute names the model by its full path, because the macro
reaches the model's events through the module the model is in.
// src/observers/user_observer.rs
use ;
use crateUser;
;
make:policy
Writes src/policies/<snake>.rs, a #[policy] for a model with the seven
abilities of Laravel's model policy stub. A Policy suffix is appended when
the name lacks one, and the model is the name without it; --model <Model>
names another. The user is crate::models::user::User. Every method
denies until you write its rule, as Laravel's stub returns false.
// src/policies/note_policy.rs
use policy;
use crateUser;
use crateNote;
;
update registers the gate update-note; see
Authorization. A policy of User
itself names its second parameter _model, as Laravel's stub does.
make:provider
Writes src/providers/<snake>.rs: a pub async fn register() for a group
of service registrations, which your bootstrap calls.
// src/providers/payment_service_provider.rs
pub async
Call it from register in src/bootstrap.rs:
crateregister.await;
Why Suprnova diverges
Laravel's make:provider writes a ServiceProvider class with register
and boot methods and adds it to bootstrap/providers.php. Suprnova has no
provider class: Application Bootstrap
explains why register and register_http_stack replace the provider
list. What plays a provider's part is a public register function that
src/bootstrap.rs calls, so that is the stub. The command does not edit
your bootstrap, because where the call goes in the boot order is your
decision; it prints the line to add.
make:resource
Writes src/resources/<snake>.rs, a JSON:API resource:
a #[derive(Data)] struct with #[json_resource]. A Resource suffix is
appended when the name lacks one. The JSON:API type is the plural of the
name's words in kebab case: User is users, UserProfile is
user-profiles.
// src/resources/user_resource.rs
use Data;
use Validate;
Add the attributes as fields, and render it with
Resource::single(resource).render().await or Resource::collection.
make:rule
Writes src/rules/<snake>.rs, a custom validation rule
that passes every value until you write its check, as Laravel's rule stub
does.
// src/rules/uppercase.rs
use ;
;
Use it in a validate! row: name => Required, Uppercase;.
make:scope
Writes src/scopes/<snake>.rs, a global scope
that adds nothing to the query yet. A Scope suffix is appended when the
name lacks one. A scope's name does not say which model it scopes, so the
model is User unless --model <Model> names another.
// src/scopes/published_scope.rs
use ;
use cratePost;
;
Register it at boot: ScopeRegistry::register::<Post, _>(PublishedScope);.
make:test
Writes tests/<snake>.rs, an integration test that sends a request through
TestClient and asserts the answer, as Laravel's feature test stub does.
It declares no module. See HTTP Tests for the client.
// tests/user_test.rs
use TestClient;
use ;
async
async
make:trait
Writes src/concerns/<snake>.rs, as Laravel writes traits under
app/Concerns: a trait with a default method, which every implementor gets
without writing it.
// src/concerns/has_uuid.rs
make:interface
Writes src/contracts/<snake>.rs, as Laravel writes interfaces under
app/Contracts: a trait whose methods are all required, with Send + Sync
so an implementor can be bound in the container as Arc<dyn PaymentGateway>.
// src/contracts/payment_gateway.rs
Why Suprnova diverges
PHP has two constructs where Rust has one. A PHP trait carries method
bodies that are copied into the class that uses it; a PHP interface names
methods without bodies. Rust's trait does both: a method with a body is a
default that an implementor may override, and a method without one is
required. So make:trait and make:interface both write a trait.
make:trait gives it a default method and make:interface gives it
required methods only, and each goes in the directory Laravel uses for its
construct.
make:model
There is no make:model. Write the model by hand with #[suprnova::model],
or let db:sync --regenerate-models write it from the schema after a
migration runs. See Eloquent and
CLI Migrations.
Why Suprnova diverges
Laravel's make:model writes an empty class, because Eloquent reads the
columns from the database at run time. A Suprnova model is a struct whose
fields are its columns, checked when the crate compiles, so an empty model
is not a model. The struct has to name the columns, which either you or
db:sync know and a generator does not. make:migration writes the schema
change, and db:sync turns the migrated schema into model files.
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, src/lib.rs and cmd/main.rs as well, and for make:view,
src/lib.rs). A nested name declares every module on the way to its
file, since each directory is a module of its own.
Laravel's --test writes a PHPUnit or Pest test under tests/Feature.
Suprnova's writes a Rust integration test under tests/, one file per
generated item, which imports the item from your library crate. 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:middleware <Dir>/<Name> --test |
src/middleware/<dir>/<snake>.rs, tests/<dir>_<snake>_middleware.rs |
middleware/<dir>/mod.rs, middleware/mod.rs |
make:view <dir>.<name> |
templates/<dir>/<name>.html, src/views/<dir>/<snake>.rs |
views/<dir>/mod.rs, views/mod.rs, lib.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 <Dir>/<Name> --test |
frontend/src/pages/<Dir>/<Name>Page.<ext>, tests/<dir>_<name>_page.rs |
(no module wiring) |
make:inertia <Name> --data |
src/props/<snake>.rs |
(no module wiring; warns when src/props/ is new) |
make:migration <name> |
migrations/YYYYMMDDHHMMSS_<name>.rs |
(no module wiring) |
make:factory <Name> [--model <Model>] |
src/factories/<snake>_factory.rs |
factories/mod.rs, lib.rs |
make:seeder <Name> |
src/seeders/<snake>_seeder.rs |
seeders/mod.rs, lib.rs |
make:cast <Name> |
src/casts/<snake>.rs |
casts/mod.rs, lib.rs |
make:channel <Name> |
src/broadcasting/<snake>.rs |
broadcasting/mod.rs, lib.rs |
make:class <Name> |
src/<snake>.rs |
lib.rs |
make:enum <Name> |
src/enums/<snake>.rs |
enums/mod.rs, lib.rs |
make:event <Name> |
src/events/<snake>.rs |
events/mod.rs, lib.rs |
make:listener <Name> [--event <Event>] |
src/listeners/<snake>.rs |
listeners/mod.rs, lib.rs |
make:job <Name> |
src/jobs/<snake>.rs |
jobs/mod.rs, lib.rs |
make:job-middleware <Name> |
src/jobs/middleware/<snake>.rs |
jobs/middleware/mod.rs, jobs/mod.rs, lib.rs |
make:mail <Name> |
src/mail/<snake>.rs |
mail/mod.rs, lib.rs |
make:notification <Name> |
src/notifications/<snake>.rs |
notifications/mod.rs, lib.rs |
make:observer <Name> [--model <Model>] |
src/observers/<snake>_observer.rs |
observers/mod.rs, lib.rs |
make:policy <Name> [--model <Model>] |
src/policies/<snake>_policy.rs |
policies/mod.rs, lib.rs |
make:provider <Name> |
src/providers/<snake>.rs |
providers/mod.rs, lib.rs |
make:resource <Name> |
src/resources/<snake>_resource.rs |
resources/mod.rs, lib.rs |
make:rule <Name> |
src/rules/<snake>.rs |
rules/mod.rs, lib.rs |
make:scope <Name> [--model <Model>] |
src/scopes/<snake>_scope.rs |
scopes/mod.rs, lib.rs |
make:test <Name> |
tests/<snake>.rs |
(no module wiring) |
make:trait <Name> |
src/concerns/<snake>.rs |
concerns/mod.rs, lib.rs |
make:interface <Name> |
src/contracts/<snake>.rs |
contracts/mod.rs, lib.rs |
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 - Eloquent Factories and Seeding -
what
make:factoryandmake:seederwrite fills in - Events and Queues - registering the events, listeners and jobs the generators write
