nest-rs-cli 1.0.0

Scaffolding CLI for NestRS — new projects, feature generators, and project health checks.
//! **Workspace mode** — monorepo: `crates/features/` + thin `apps/*` binaries.
//!
//! Generated by `nestrs new <name>`. Product logic lives in
//! features; each app root `module.rs` composes edge modules only.
//! Contrasts with [`super::standalone`] (single crate, logic in `src/`).

/// Root `Cargo.toml` for a greenfield NestRS monorepo (crates.io deps).
pub const ROOT_CARGO: &str = r#"[workspace]
resolver = "2"
members = ["crates/*", "apps/*"]

[workspace.package]
version = "0.1.0"
edition = "2024"
rust-version = "1.96"

[workspace.dependencies]
anyhow = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
features = { path = "crates/features" }
nest-rs-core = "{{nestrs_version}}"
nest-rs-config = "{{nestrs_version}}"
nest-rs-guards = "{{nestrs_version}}"
nest-rs-http = "{{nestrs_version}}"
nest-rs-interceptors = "{{nestrs_version}}"
nest-rs-testing = "{{nestrs_version}}"
poem = { version = "3", features = ["tower-compat", "anyhow", "rustls"] }

# Release: the smallest, fastest single binary — production defaults.
[profile.release]
opt-level = 3       # maximum runtime performance
lto = "fat"         # whole-program optimisation across all crates
codegen-units = 1   # give the optimiser the whole crate at once
strip = true        # drop symbols for a smaller binary

# Dev: fastest iterative rebuilds; keep file:line in backtraces.
[profile.dev]
debug = "line-tables-only"
"#;

pub const FEATURES_CARGO: &str = r#"[package]
name = "features"
version.workspace = true
edition.workspace = true
publish = false

[dependencies]
nest-rs-core.workspace = true
nest-rs-guards.workspace = true
nest-rs-http.workspace = true
nest-rs-interceptors.workspace = true
poem.workspace = true
"#;

pub const FEATURES_LIB: &str = r#"//! Product features — vertical slices shared across apps.
//!
//! Add one with: `nestrs g feature <name>` (port) or `nestrs g feature <name> --http`.
"#;

pub const FEATURES_LIB_WITH_HELLO: &str = r#"//! Product features — vertical slices shared across apps.

pub mod hello;

pub use hello::HelloHttpModule;
"#;

pub const HELLO_MOD: &str = r#"mod module;
mod service;

pub mod http;

pub use http::HelloHttpModule;
pub use module::HelloModule;
pub use service::HelloService;
"#;

pub const HELLO_MODULE: &str = r#"use nest_rs_core::module;

use super::service::HelloService;

#[module(providers = [HelloService])]
pub struct HelloModule;
"#;

pub const HELLO_SERVICE: &str = r#"use nest_rs_core::injectable;

#[injectable]
#[derive(Default)]
pub struct HelloService;

impl HelloService {
    pub fn greeting(&self) -> String {
        "Hello World".to_string()
    }
}
"#;

pub const HELLO_HTTP_MOD: &str = r#"mod controller;
mod module;

pub use controller::HelloController;
pub use module::HelloHttpModule;
"#;

pub const HELLO_HTTP_MODULE: &str = r#"use nest_rs_core::module;

use super::controller::HelloController;
use crate::hello::HelloModule;

#[module(
    imports = [HelloModule],
    providers = [HelloController],
)]
pub struct HelloHttpModule;
"#;

pub const HELLO_HTTP_CONTROLLER: &str = r#"use std::sync::Arc;

use nest_rs_http::{controller, routes};

use crate::hello::HelloService;

#[controller(path = "/")]
pub struct HelloController {
    #[inject]
    svc: Arc<HelloService>,
}

#[routes]
impl HelloController {
    #[get("/")]
    async fn hello(&self) -> String {
        self.svc.greeting()
    }
}
"#;

/// Thin workspace app — composition only (`apps/api` shape).
pub const APP_CARGO: &str = r#"[package]
name = "{{kebab}}"
version.workspace = true
edition.workspace = true
publish = false

[dependencies]
features.workspace = true
nest-rs-core.workspace = true
nest-rs-config.workspace = true
nest-rs-http.workspace = true
tokio.workspace = true
anyhow.workspace = true

[dev-dependencies]
nest-rs-testing.workspace = true
"#;

pub const APP_LIB: &str = r#"mod module;

pub use module::{{module}};
"#;

pub const APP_MAIN: &str = r#"use anyhow::Result;
use nest_rs_config::Environment;
use nest_rs_core::App;

use {{snake}}::{{module}};

#[tokio::main]
async fn main() -> Result<()> {
    let _environment = Environment::init();

    App::builder()
        .module::<{{module}}>()
        .build()
        .await?
        .run()
        .await
}
"#;

pub const APP_MODULE_WITH_HELLO: &str = r#"use nest_rs_core::module;
use nest_rs_http::{HttpConfig, HttpModule};

use features::hello::HelloHttpModule;

#[module(
    imports = [
        HttpModule::for_root(HttpConfig { port: {{port}}, ..Default::default() }),
        HelloHttpModule,
    ],
)]
pub struct {{module}};
"#;

/// Thin workspace app — composition only; HTTP port pinned in code (see `apps/api`).
pub const APP_MODULE: &str = r#"use nest_rs_core::module;
use nest_rs_http::{HttpConfig, HttpModule};

#[module(imports = [
    HttpModule::for_root(HttpConfig { port: {{port}}, ..Default::default() }),
])]
pub struct {{module}};
"#;

pub const APP_SMOKE: &str = r#"//! In-process smoke test — boots the real DI graph through `TestApp`, no live
//! infra, so it belongs to the `integration` suite and runs on every
//! `nestrs run test unit`. Add a `tests/e2e/main.rs` suite when the app grows
//! a database, queue or storage dependency.

use {{snake}}::{{module}};
use nest_rs_testing::TestApp;

#[tokio::test]
async fn hello_endpoint_greets() {
    let app = TestApp::builder()
        .with_test_telemetry()
        .module::<{{module}}>()
        .build()
        .await
        .expect("{{module}} boots and mounts its routes");

    let resp = app.http().get("/").send().await;
    resp.assert_status_is_ok();
    resp.assert_text("Hello World").await;
}
"#;

pub const README: &str = r#"# {{kebab}}

**Workspace** NestRS monorepo — product logic in `crates/features/`, thin
composition apps under `apps/`. Generated by `nestrs new {{kebab}}`.

## Run the default app

```bash
nestrs run dev hello
nestrs run build              # default app → target/release/hello
nestrs run build blog         # one app by name
nestrs run build --all        # every app
```

Open **http://localhost:3000/** in your browser — `Hello World` from
`crates/features/src/hello/`. `apps/hello/` only wires modules; the listen
port is set in `apps/hello/src/module.rs`, not in `.env`.

Need a single crate instead? Use
[standalone mode](/cli/#layout-detection): `nestrs new <name> --standalone`.

## Grow the workspace

```bash
nestrs new blog
nestrs g resource posts
```

Import edge modules in each app's root `module.rs` — see
[`users/`](https://github.com/YV17labs/NestRS/tree/main/crates/features/src/users)
in the framework repo.
"#;

pub const JUSTFILE: &str = r#"_default:
    @just --list

# Run an app with auto-reload — watches the source, rebuilds and restarts on
# save. Default: hello. Usage: nestrs run dev blog
dev app="hello":
    bacon run-long -- --bin {{app}}

# Run an app in release mode. Usage: nestrs run start blog
start app="hello":
    cargo run --release --bin {{app}}

# Build in release: one app (default hello), or every app with `--all`.
# Usage: nestrs run build blog   |   nestrs run build --all
build app="hello":
    cargo build --release {{ if app == "--all" { "--workspace" } else { "-p " + app } }}

# Type-check the workspace.
check:
    cargo check --workspace

# Tests — unit/integration/e2e/doctests. Usage: nestrs run test [unit|e2e|doc]
mod test

# Database lifecycle. Usage: nestrs run db up|down|fresh|status|seed|reset
mod db

# Apply rustfmt across the workspace.
fmt:
    cargo fmt --all

# Clippy (strict) + format check.
lint:
    cargo clippy --workspace --all-targets -- -D warnings
    cargo fmt --all --check
"#;

pub const TEST_JUSTFILE: &str = r#"# Test recipes, exposed as `nestrs run test <kind>` (see `mod test` in the
# Justfile). Bare `nestrs run test` lists these — pick a kind to run.

# Bare `nestrs run test` lists the kinds instead of running one.
_default:
    @just --list test

# Unit + integration + doctests, no DB.
unit:
    cargo nextest run --workspace -E 'not binary(e2e)'
    cargo test --workspace --doc          # nextest skips doctests; run them too

# e2e tests — your `tests/e2e/main.rs` binaries (live Postgres/Redis if the
# app needs them). None scaffolded yet, so `--no-tests=pass` keeps this green
# until an app adds its suite.
e2e:
    cargo nextest run --workspace -E 'binary(e2e)' --no-tests=pass

# Doctests only — the code examples inside `///` doc comments.
doc:
    cargo test --workspace --doc
"#;