reinhardt_commands/lib.rs
1#![warn(missing_docs)]
2//! # Reinhardt Management Commands
3//!
4//! Django-style management command framework for Reinhardt.
5//!
6//! ## Features
7//!
8//! - **BaseCommand**: Trait for creating custom commands
9//! - **Standard Commands**: migrate, shell, runserver, etc.
10//! - **Argument Parsing**: Clap-based argument handling
11//! - **Command Registry**: Automatic command discovery
12//! - **Interactive Mode**: Support for interactive prompts
13//! - **Colored Output**: Rich terminal output
14//! - **AST-Based Code Generation**: Robust code generation using Abstract Syntax Trees
15//! - **Auto-Reload**: Built-in hot-reload for the development server (server + wasm)
16//! - **Tera Template Engine**: Powerful template rendering for project/app generation
17//!
18//! ## Example
19//!
20//! ```rust,no_run
21//! # use reinhardt_commands::{BaseCommand, CommandContext, CommandResult};
22//! # #[tokio::main]
23//! # async fn main() {
24//! // struct MyCommand;
25//! //
26//! // #[async_trait]
27//! // impl BaseCommand for MyCommand {
28//! // fn name(&self) -> &str {
29//! // "mycommand"
30//! // }
31//! //
32//! // async fn execute(&self, ctx: &CommandContext) -> CommandResult<()> {
33//! // println!("Hello from my command!");
34//! // Ok(())
35//! // }
36//! // }
37//! # }
38//! ```
39//!
40//! ## Template System
41//!
42//! The command framework uses [Tera](https://keats.github.io/tera/) for template rendering.
43//! Tera is a powerful template engine inspired by Jinja2/Django templates.
44//!
45//! ### Template Context
46//!
47//! Templates receive context variables through `TemplateContext`:
48//!
49//! ```rust
50//! use reinhardt_commands::TemplateContext;
51//!
52//! let mut context = TemplateContext::new();
53//! context.insert("project_name", "my_project").unwrap();
54//! context.insert("version", "1.0.0").unwrap();
55//! context.insert("features", vec!["auth", "admin"]).unwrap(); // Any Serialize type
56//! ```
57//!
58//! ### Template Variables
59//!
60//! The `insert` method accepts any type implementing `serde::Serialize`
61//! and returns `Result<(), serde_json::Error>`:
62//!
63//! - Strings: `context.insert("name", "value")?`
64//! - Numbers: `context.insert("count", 42)?`
65//! - Booleans: `context.insert("enabled", true)?`
66//! - Collections: `context.insert("items", vec!["a", "b"])?`
67//! - Custom types: `context.insert("data", &my_struct)?`
68//!
69//! ## AST-Based Code Generation
70//!
71//! The `startapp` command uses Abstract Syntax Tree (AST) parsing via `syn` and `quote`
72//! for robust code generation and modification. This approach provides several benefits:
73//!
74//! ### Benefits of AST Approach
75//!
76//! 1. **Syntax Awareness**: Understands code structure, not just text patterns
77//! - Correctly distinguishes `pub mod app;` from `// pub mod app;` (commented)
78//! - Handles variations in whitespace and formatting automatically
79//!
80//! 2. **Duplicate Detection**: Structurally detects existing declarations
81//! - Avoids adding duplicate module declarations
82//! - Works correctly even with complex existing code
83//!
84//! 3. **Consistent Formatting**: Uses `prettyplease` for standardized output
85//! - Ensures consistent code style across generated files
86//! - Integrates well with rustfmt
87//!
88//! ### Example: apps.rs Generation
89//!
90//! When you run `startapp myapp`, the command:
91//! 1. Parses existing `src/apps.rs` using `syn::parse_file`
92//! 2. Checks for existing `pub mod myapp;` declaration structurally
93//! 3. Adds new module and use declarations if not present
94//! 4. Formats output with `prettyplease::unparse`
95//!
96//! ```rust,ignore
97//! // Generated apps.rs
98//! pub mod myapp;
99//! pub use myapp::MyappConfig;
100//! ```
101//!
102//! This is more reliable than string-based approaches that can be confused by
103//! comments, unusual formatting, or complex code patterns.
104//!
105//! ## Auto-Reload for Development Server
106//!
107//! The `runserver` command reloads automatically on file changes. No external
108//! tool (such as `cargo-watch` or `bacon`) is required — the watcher is built
109//! into the `autoreload` feature.
110//!
111//! ```text
112//! cargo run --bin manage -- runserver --with-pages
113//! ```
114//!
115//! Edit any Rust source file (server-side or wasm-side) and the bundle plus
116//! the server are rebuilt in place. Pass `--noreload` to disable auto-reload
117//! entirely, or `--no-wasm-rebuild` to keep server reload but manage the wasm
118//! build yourself. The server restart success log is emitted only after the
119//! respawned child accepts connections at the advertised development address.
120//!
121//! See [`runserver_hooks`] for the full hot-reload runbook and failure modes.
122
123/// Base command trait and argument/option definitions.
124pub mod base;
125/// Built-in management commands (migrate, runserver, shell, etc.).
126pub mod builtin;
127/// CLI argument parsing and command dispatch.
128pub mod cli;
129/// Static file collection command.
130pub mod collectstatic;
131/// Command execution context (settings, output, verbosity).
132pub mod context;
133/// Superuser creation command.
134#[cfg(feature = "auth")]
135pub(crate) mod createsuperuser;
136/// Debounced file-system watcher for hot-reload (replaces inline watcher).
137#[cfg(feature = "autoreload")]
138#[doc(hidden)]
139pub mod debounced_watcher;
140/// Embedded Tera templates for project/app scaffolding.
141pub mod embedded_templates;
142/// Code formatting utilities for generated code.
143pub mod formatter;
144/// Internationalization commands (makemessages, compilemessages).
145pub mod i18n_commands;
146/// Project introspection command for platform metadata discovery.
147#[cfg(feature = "introspect")]
148pub mod introspect;
149/// Local development infrastructure management.
150pub mod local_infra;
151/// Email testing command.
152pub mod mail_commands;
153/// Terminal output wrapper with styling support.
154pub mod output;
155/// Compile-free static Pages hot patch support.
156#[cfg(all(feature = "autoreload", feature = "pages"))]
157mod page_hot_patch;
158/// Plugin management commands.
159#[cfg(feature = "plugins")]
160pub mod plugin_commands;
161mod process;
162/// Project dependency configuration commands.
163pub mod project_config;
164/// Command registry for discovery and dispatch.
165pub mod registry;
166/// Runserver lifecycle hooks for concurrent services and pre-listen validation.
167#[cfg(feature = "server")]
168pub mod runserver_hooks;
169/// Hot-reload server rebuild pipeline (cargo build + child process swap).
170#[cfg(feature = "autoreload")]
171#[doc(hidden)]
172pub mod server_rebuild_pipeline;
173/// Source-tree enumeration for hot-reload watch targets.
174#[cfg(feature = "autoreload")]
175#[doc(hidden)]
176pub mod source_roots;
177/// Project and app scaffolding commands (startproject, startapp).
178pub mod start_commands;
179/// Template-based code generation utilities.
180pub mod template;
181/// Template source abstraction over embedded and filesystem assets.
182pub mod template_source;
183/// WASM build tooling for client-side compilation.
184pub mod wasm_builder;
185/// Hot-reload WASM rebuild pipeline (timing + structured logging wrapper).
186#[cfg(all(feature = "autoreload", feature = "pages"))]
187#[doc(hidden)]
188pub mod wasm_rebuild_pipeline;
189/// Development server welcome page.
190pub mod welcome_page;
191
192/// Internal test surface for the hot-reload integration tests.
193///
194/// This module is intentionally `#[doc(hidden)]` and re-exports the otherwise
195/// crate-private hot-reload pieces so that integration tests living under
196/// `tests/` (a separate crate target) can drive them end-to-end. It is not
197/// part of the public API and may change without notice.
198#[cfg(feature = "autoreload")]
199#[doc(hidden)]
200pub mod __hot_reload_test_api {
201 pub use crate::debounced_watcher::{
202 DEBOUNCE_WINDOW, RebuildTargets, WatcherConfig, debounce_next, is_relevant_change,
203 rebuild_targets_for_paths, run_rebuild_for_paths, run_watcher,
204 };
205 #[cfg(all(feature = "autoreload", feature = "pages"))]
206 pub use crate::page_hot_patch::render_static_page_patch;
207 pub use crate::server_rebuild_pipeline::{ServerRebuildOutcome, ServerRebuildPipeline};
208 pub use crate::source_roots::SourceRoots;
209 #[cfg(feature = "pages")]
210 pub use crate::wasm_rebuild_pipeline::{WasmRebuildOutcome, WasmRebuildPipeline};
211
212 /// HR-8 regression entry point (#4244): exercise only the
213 /// autoreload-parent validation step. Wraps the crate-private
214 /// `RunServerCommand::validate_hooks_only_for_tests` so the surface stays
215 /// inside `__hot_reload_test_api` instead of widening
216 /// `RunServerCommand`'s public API.
217 #[cfg(feature = "server")]
218 pub async fn validate_hooks_only(ctx: &crate::CommandContext) -> crate::CommandResult<()> {
219 crate::RunServerCommand::validate_hooks_only_for_tests(ctx).await
220 }
221}
222
223use thiserror::Error;
224
225pub use base::{BaseCommand, CommandArgument, CommandOption};
226#[cfg(feature = "migrations")]
227pub use builtin::MakeMigrationsCommand;
228#[cfg(feature = "routers")]
229pub use builtin::ShowUrlsCommand;
230pub use builtin::{CheckCommand, CheckDiCommand, MigrateCommand, RunServerCommand, ShellCommand};
231#[cfg(feature = "server")]
232pub use cli::start_server;
233pub use cli::{
234 Cli, Commands, auto_register_router, execute_from_command_line,
235 execute_from_command_line_with_registry, execute_from_command_line_with_registry_and_settings,
236 execute_from_command_line_with_settings, run_command, run_command_with_registry,
237};
238pub use collectstatic::{CollectStaticCommand, CollectStaticOptions, CollectStaticStats};
239pub use context::CommandContext;
240pub use i18n_commands::{CompileMessagesCommand, MakeMessagesCommand};
241#[cfg(feature = "introspect")]
242pub use introspect::IntrospectCommand;
243pub use mail_commands::SendTestEmailCommand;
244pub use output::OutputWrapper;
245pub use project_config::{ConfigureCommand, ReinhardtDependencySelection};
246pub use registry::CommandRegistry;
247#[cfg(feature = "server")]
248pub use runserver_hooks::{RunserverContext, RunserverHook, RunserverHookRegistration};
249pub use start_commands::{StartAppCommand, StartProjectCommand};
250pub use template::{TemplateCommand, TemplateContext, generate_secret_key, to_camel_case};
251pub use wasm_builder::{
252 WasmBuildConfig, WasmBuildError, WasmBuildOutput, WasmBuilder, check_wasm_tools_installed,
253 detect_cdylib_in_cargo_toml, detect_cdylib_in_cargo_toml_content, is_wasm_stale,
254 latest_source_mtime,
255};
256pub use welcome_page::WelcomePage;
257
258#[cfg(feature = "plugins")]
259pub use plugin_commands::{
260 PluginDisableCommand, PluginEnableCommand, PluginInfoCommand, PluginInstallCommand,
261 PluginListCommand, PluginRemoveCommand, PluginSearchCommand, PluginUpdateCommand,
262};
263
264/// Errors that can occur during management command execution.
265#[derive(Debug, Error)]
266pub enum CommandError {
267 /// The requested command was not found in the registry.
268 #[error("Command not found: {0}")]
269 NotFound(String),
270
271 /// The provided command arguments are invalid.
272 #[error("Invalid arguments: {0}")]
273 InvalidArguments(String),
274
275 /// A runtime error occurred during command execution.
276 #[error("Execution error: {0}")]
277 ExecutionError(String),
278
279 /// An I/O error occurred.
280 #[error("IO error: {0}")]
281 IoError(#[from] std::io::Error),
282
283 /// An error occurred while parsing command input.
284 #[error("Parse error: {0}")]
285 ParseError(String),
286
287 /// A template rendering error occurred.
288 #[error("Template error: {0}")]
289 TemplateError(String),
290}
291
292impl From<tera::Error> for CommandError {
293 fn from(err: tera::Error) -> Self {
294 CommandError::TemplateError(err.to_string())
295 }
296}
297
298impl From<String> for CommandError {
299 fn from(err: String) -> Self {
300 CommandError::ExecutionError(err)
301 }
302}
303
304impl From<serde_json::Error> for CommandError {
305 fn from(err: serde_json::Error) -> Self {
306 CommandError::ExecutionError(format!("Serialization error: {}", err))
307 }
308}
309
310/// A specialized `Result` type for management command operations.
311pub type CommandResult<T> = std::result::Result<T, CommandError>;