From 6d59f689786a93620f2f050b88febb19a7e2adfb Mon Sep 17 00:00:00 2001 From: "Dotan J. Nahum" Date: Tue, 22 Oct 2024 09:07:36 +0300 Subject: [PATCH 1/5] Generators testing toolkit (#887) * refactor: move gen into a crate - loco-gen + better seperation * implement a generators testing kit similar to Rails' testing rig * separate gen CI from loco CI --- .github/workflows/ci-loco-gen.yml | 83 ++++++ .github/workflows/ci.yml | 3 +- .gitignore | 1 + Cargo.toml | 38 ++- examples/demo/Cargo.lock | 9 - loco-gen/Cargo.toml | 32 +++ {src/gen => loco-gen/src}/controller.rs | 14 +- src/gen/mod.rs => loco-gen/src/lib.rs | 115 +++++--- {src/gen => loco-gen/src}/mappings.json | 0 {src/gen => loco-gen/src}/model.rs | 78 +++++- {src/gen => loco-gen/src}/scaffold.rs | 12 +- .../src}/templates/controller.t | 0 .../templates/controller/api/controller.t | 0 .../src}/templates/controller/api/test.t | 0 .../templates/controller/html/controller.t | 0 .../src}/templates/controller/html/view.t | 0 .../templates/controller/htmx/controller.t | 0 .../src}/templates/controller/htmx/view.t | 0 .../src}/templates/deployment_docker.t | 0 .../src}/templates/deployment_docker_ignore.t | 0 .../src}/templates/deployment_nginx.t | 0 .../src}/templates/deployment_shuttle.t | 0 .../templates/deployment_shuttle_config.t | 0 {src/gen => loco-gen/src}/templates/mailer.t | 0 .../src}/templates/mailer_html.t | 0 .../src}/templates/mailer_sub.t | 0 .../src}/templates/mailer_text.t | 0 .../src}/templates/migration.t | 0 {src/gen => loco-gen/src}/templates/model.t | 0 .../src}/templates/model_test.t | 0 .../src}/templates/request_test.t | 0 .../src}/templates/scaffold/api/controller.t | 0 .../src}/templates/scaffold/api/test.t | 0 .../src}/templates/scaffold/html/base.t | 0 .../src}/templates/scaffold/html/controller.t | 0 .../src}/templates/scaffold/html/view.t | 0 .../templates/scaffold/html/view_create.t | 0 .../src}/templates/scaffold/html/view_edit.t | 0 .../src}/templates/scaffold/html/view_list.t | 0 .../src}/templates/scaffold/html/view_show.t | 0 .../src}/templates/scaffold/htmx/base.t | 0 .../src}/templates/scaffold/htmx/controller.t | 0 .../src}/templates/scaffold/htmx/view.t | 0 .../templates/scaffold/htmx/view_create.t | 0 .../src}/templates/scaffold/htmx/view_edit.t | 0 .../src}/templates/scaffold/htmx/view_list.t | 0 .../src}/templates/scaffold/htmx/view_show.t | 0 .../src}/templates/scheduler.t | 0 {src/gen => loco-gen/src}/templates/task.t | 0 .../src}/templates/task_test.t | 0 {src/gen => loco-gen/src}/templates/worker.t | 0 .../src}/templates/worker_test.t | 0 loco-gen/src/testutil.rs | 265 ++++++++++++++++++ src/cli.rs | 75 +++-- src/errors.rs | 6 +- src/lib.rs | 1 - 56 files changed, 622 insertions(+), 110 deletions(-) create mode 100644 .github/workflows/ci-loco-gen.yml create mode 100644 loco-gen/Cargo.toml rename {src/gen => loco-gen/src}/controller.rs (90%) rename src/gen/mod.rs => loco-gen/src/lib.rs (76%) rename {src/gen => loco-gen/src}/mappings.json (100%) rename {src/gen => loco-gen/src}/model.rs (55%) rename {src/gen => loco-gen/src}/scaffold.rs (94%) rename {src/gen => loco-gen/src}/templates/controller.t (100%) rename {src/gen => loco-gen/src}/templates/controller/api/controller.t (100%) rename {src/gen => loco-gen/src}/templates/controller/api/test.t (100%) rename {src/gen => loco-gen/src}/templates/controller/html/controller.t (100%) rename {src/gen => loco-gen/src}/templates/controller/html/view.t (100%) rename {src/gen => loco-gen/src}/templates/controller/htmx/controller.t (100%) rename {src/gen => loco-gen/src}/templates/controller/htmx/view.t (100%) rename {src/gen => loco-gen/src}/templates/deployment_docker.t (100%) rename {src/gen => loco-gen/src}/templates/deployment_docker_ignore.t (100%) rename {src/gen => loco-gen/src}/templates/deployment_nginx.t (100%) rename {src/gen => loco-gen/src}/templates/deployment_shuttle.t (100%) rename {src/gen => loco-gen/src}/templates/deployment_shuttle_config.t (100%) rename {src/gen => loco-gen/src}/templates/mailer.t (100%) rename {src/gen => loco-gen/src}/templates/mailer_html.t (100%) rename {src/gen => loco-gen/src}/templates/mailer_sub.t (100%) rename {src/gen => loco-gen/src}/templates/mailer_text.t (100%) rename {src/gen => loco-gen/src}/templates/migration.t (100%) rename {src/gen => loco-gen/src}/templates/model.t (100%) rename {src/gen => loco-gen/src}/templates/model_test.t (100%) rename {src/gen => loco-gen/src}/templates/request_test.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/api/controller.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/api/test.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/base.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/controller.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/view.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/view_create.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/view_edit.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/view_list.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/html/view_show.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/base.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/controller.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/view.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/view_create.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/view_edit.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/view_list.t (100%) rename {src/gen => loco-gen/src}/templates/scaffold/htmx/view_show.t (100%) rename {src/gen => loco-gen/src}/templates/scheduler.t (100%) rename {src/gen => loco-gen/src}/templates/task.t (100%) rename {src/gen => loco-gen/src}/templates/task_test.t (100%) rename {src/gen => loco-gen/src}/templates/worker.t (100%) rename {src/gen => loco-gen/src}/templates/worker_test.t (100%) create mode 100644 loco-gen/src/testutil.rs diff --git a/.github/workflows/ci-loco-gen.yml b/.github/workflows/ci-loco-gen.yml new file mode 100644 index 000000000..4ea15d376 --- /dev/null +++ b/.github/workflows/ci-loco-gen.yml @@ -0,0 +1,83 @@ +name: CI/loco-gen + +on: + push: + branches: + - master + pull_request: + +env: + RUST_TOOLCHAIN: stable + TOOLCHAIN_PROFILE: minimal + +defaults: + run: + working-directory: ./loco-gen + +jobs: + rustfmt: + name: Check Style + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + components: rustfmt + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Run Clippy + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms + + test: + name: Run Tests + needs: [rustfmt, clippy] + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + + - run: | + cargo install --path ../loco-cli + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --all diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e1e74250..955fec3d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,8 +68,9 @@ jobs: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 + - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - args: --all-features --all + args: --all-features --workspace --exclude loco-gen diff --git a/.gitignore b/.gitignore index 8309fa2ae..d10f63005 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # local dev todo.txt +todo.md examples/demo2 *.sqlite *.sqlite-wal diff --git a/Cargo.toml b/Cargo.toml index 86200dedc..c72e26bae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["xtask", "loco-extras"] +members = ["xtask", "loco-extras", "loco-gen"] exclude = ["starters"] [workspace.package] @@ -26,7 +26,7 @@ default = ["auth_jwt", "cli", "with-db", "cache_inmem", "bg_redis", "bg_pg"] auth_jwt = ["dep:jsonwebtoken"] cli = ["dep:clap"] testing = ["dep:axum-test"] -with-db = ["dep:sea-orm", "dep:sea-orm-migration"] +with-db = ["dep:sea-orm", "dep:sea-orm-migration", "loco-gen/with-db"] channels = ["dep:socketioxide"] # Storage features all_storage = ["storage_aws_s3", "storage_azure", "storage_gcp"] @@ -39,6 +39,7 @@ bg_redis = ["dep:rusty-sidekiq", "dep:bb8"] bg_pg = ["dep:sqlx", "dep:ulid"] [dependencies] +loco-gen = { path = "./loco-gen" } backtrace_printer = { version = "1.3.0" } # cli @@ -56,8 +57,8 @@ sea-orm = { version = "1.0.0", features = [ tokio = { version = "1.33.0", default-features = false } # the rest -serde = "1" -serde_json = "1" +serde = { workspace = true } +serde_json = { workspace = true } serde_yaml = "0.9" serde_variant = "0.1.2" @@ -66,8 +67,8 @@ async-trait = { workspace = true } axum = { workspace = true } axum-extra = { version = "0.9", features = ["cookie"] } -regex = "1" -lazy_static = "1.4.0" +regex = { workspace = true } +lazy_static = { workspace = true } fs-err = "2.11.0" # mailer tera = "1.19.1" @@ -79,8 +80,8 @@ lettre = { version = "0.11.4", default-features = false, features = [ "tokio1-rustls-tls", ] } include_dir = "0.7.3" -thiserror = "1" -tracing = "0.1.40" +thiserror = { workspace = true } +tracing = { workspace = true } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-appender = "0.2.3" @@ -103,12 +104,7 @@ ipnetwork = "0.20.0" axum-test = { version = "16.1.0", optional = true } -# gen -rrgen = "0.5.3" -chrono = "0.4.31" -cargo_metadata = "0.18.1" -dialoguer = "0.11.0" - +chrono = { workspace = true } cfg-if = "1" uuid = { version = "1.10.0", features = ["v4", "fast-rng"] } @@ -138,6 +134,14 @@ rusty-sidekiq = { version = "0.11.0", default-features = false, optional = true bb8 = { version = "0.8.1", optional = true } [workspace.dependencies] + +chrono = { version = "0.4", features = ["serde"] } +tracing = "0.1.40" +regex = "1" +thiserror = "1" +serde = "1" +serde_json = "1" +lazy_static = "1.4.0" async-trait = { version = "0.1.74" } axum = { version = "0.7.5", features = ["macros"] } tower = "0.4" @@ -168,6 +172,7 @@ features = [ features = ["testing"] [dev-dependencies] +cargo_metadata = "0.18.1" loco-rs = { path = ".", features = ["testing"] } rstest = "0.21.0" insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } @@ -175,3 +180,8 @@ tree-fs = { version = "0.1.0" } reqwest = { version = "0.12.7" } serial_test = "3.1.1" tower = { workspace = true, features = ["util"] } + +# generator tests +tempfile = "3" +duct_sh = { version = "0.13.7" } +syn = { version = "2", features = ["full"] } diff --git a/examples/demo/Cargo.lock b/examples/demo/Cargo.lock index e35671dac..3d5d8fa95 100644 --- a/examples/demo/Cargo.lock +++ b/examples/demo/Cargo.lock @@ -1433,7 +1433,6 @@ dependencies = [ "include_dir", "insta", "loco-extras", - "loco-macros", "loco-rs", "migration", "rstest", @@ -2984,14 +2983,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "loco-macros" -version = "0.1.0" -dependencies = [ - "quote", - "syn 2.0.66", -] - [[package]] name = "loco-rs" version = "0.11.0" diff --git a/loco-gen/Cargo.toml b/loco-gen/Cargo.toml new file mode 100644 index 000000000..eabdf162a --- /dev/null +++ b/loco-gen/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "loco-gen" +version = "0.11.0" +description = "Loco generators" +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[features] +with-db = [] + +[lib] +path = "src/lib.rs" + +[dependencies] + +lazy_static = { workspace = true } +rrgen = "0.5.3" +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +regex = { workspace = true } +tracing = { workspace = true } +chrono = { workspace = true } + +clap = { version = "4.4.7", features = ["derive"] } +dialoguer = "0.11" +duct = "0.13" + +[dev-dependencies] +tempfile = "3" +syn = { version = "2", features = ["full"] } diff --git a/src/gen/controller.rs b/loco-gen/src/controller.rs similarity index 90% rename from src/gen/controller.rs rename to loco-gen/src/controller.rs index b2947be4f..892b443e2 100644 --- a/src/gen/controller.rs +++ b/loco-gen/src/controller.rs @@ -1,7 +1,7 @@ use rrgen::RRgen; use serde_json::json; -use crate::{app::Hooks, gen}; +use crate as gen; const API_CONTROLLER_CONTROLLER_T: &str = include_str!("templates/controller/api/controller.t"); const API_CONTROLLER_TEST_T: &str = include_str!("templates/controller/api/test.t"); @@ -12,16 +12,16 @@ const HTMX_VIEW_T: &str = include_str!("templates/controller/htmx/view.t"); const HTML_CONTROLLER_CONTROLLER_T: &str = include_str!("templates/controller/html/controller.t"); const HTML_VIEW_T: &str = include_str!("templates/controller/html/view.t"); -use super::collect_messages; -use crate::Result; +use super::{collect_messages, AppInfo, Result}; -pub fn generate( +pub fn generate( rrgen: &RRgen, name: &str, actions: &[String], kind: &gen::ScaffoldKind, + appinfo: &AppInfo, ) -> Result { - let vars = json!({"name": name, "actions": actions, "pkg_name": H::app_name()}); + let vars = json!({"name": name, "actions": actions, "pkg_name": appinfo.app_name}); match kind { gen::ScaffoldKind::Api => { let res1 = rrgen.generate(API_CONTROLLER_CONTROLLER_T, &vars)?; @@ -34,7 +34,7 @@ pub fn generate( let res = rrgen.generate(HTML_CONTROLLER_CONTROLLER_T, &vars)?; messages.push(res); for action in actions { - let vars = json!({"name": name, "action": action, "pkg_name": H::app_name()}); + let vars = json!({"name": name, "action": action, "pkg_name": appinfo.app_name}); messages.push(rrgen.generate(HTML_VIEW_T, &vars)?); } Ok(collect_messages(messages)) @@ -44,7 +44,7 @@ pub fn generate( let res = rrgen.generate(HTMX_CONTROLLER_CONTROLLER_T, &vars)?; messages.push(res); for action in actions { - let vars = json!({"name": name, "action": action, "pkg_name": H::app_name()}); + let vars = json!({"name": name, "action": action, "pkg_name": appinfo.app_name}); messages.push(rrgen.generate(HTMX_VIEW_T, &vars)?); } Ok(collect_messages(messages)) diff --git a/src/gen/mod.rs b/loco-gen/src/lib.rs similarity index 76% rename from src/gen/mod.rs rename to loco-gen/src/lib.rs index d05509f6f..c59ce1e09 100644 --- a/src/gen/mod.rs +++ b/loco-gen/src/lib.rs @@ -12,10 +12,10 @@ mod controller; mod model; #[cfg(feature = "with-db")] mod scaffold; +#[cfg(test)] +mod testutil; use std::str::FromStr; -use crate::{app::Hooks, config::Config, errors, Result}; - const CONTROLLER_T: &str = include_str!("templates/controller.t"); const CONTROLLER_TEST_T: &str = include_str!("templates/request_test.t"); @@ -49,6 +49,26 @@ const DEPLOYMENT_OPTIONS: &[(&str, DeploymentKind)] = &[ ("Nginx", DeploymentKind::Nginx), ]; +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("{0}")] + Message(String), + #[error(transparent)] + RRgen(#[from] rrgen::Error), + #[error(transparent)] + IO(#[from] std::io::Error), + #[error(transparent)] + Any(#[from] Box), +} + +impl Error { + pub fn msg(err: impl std::error::Error + Send + Sync + 'static) -> Self { + Self::Message(err.to_string()) //.bt() + } +} + +pub type Result = std::result::Result; + #[derive(Serialize, Deserialize, Debug)] struct FieldType { name: String, @@ -176,11 +196,32 @@ pub enum Component { /// Name of the thing to generate name: String, }, - Deployment {}, + Deployment { + fallback_file: Option, + asset_folder: Option, + host: String, + port: i32, + }, +} +pub struct AppInfo { + pub app_name: String, } + +/// Generate a component +/// +/// # Errors +/// +/// This function will return an error if it fails #[allow(clippy::too_many_lines)] -pub fn generate(component: Component, config: &Config) -> Result<()> { +pub fn generate(component: Component, appinfo: &AppInfo) -> Result<()> { let rrgen = RRgen::default(); + /* + (1) + XXX: remove hooks generic from child generator, materialize it here and pass it + means each generator accepts a [component, config, context] tuple + this will allow us to test without an app instance + (2) proceed to test individual generators + */ match component { #[cfg(feature = "with-db")] Component::Model { @@ -191,19 +232,20 @@ pub fn generate(component: Component, config: &Config) -> Result<()> { } => { println!( "{}", - model::generate::(&rrgen, &name, link, migration_only, &fields)? + model::generate(&rrgen, &name, link, migration_only, &fields, appinfo)? ); } #[cfg(feature = "with-db")] Component::Scaffold { name, fields, kind } => { println!( "{}", - scaffold::generate::(&rrgen, &name, &fields, &kind)? + scaffold::generate(&rrgen, &name, &fields, &kind, appinfo)? ); } #[cfg(feature = "with-db")] Component::Migration { name } => { - let vars = json!({ "name": name, "ts": chrono::Utc::now(), "pkg_name": H::app_name()}); + let vars = + json!({ "name": name, "ts": chrono::Utc::now(), "pkg_name": appinfo.app_name}); rrgen.generate(MIGRATION_T, &vars)?; } Component::Controller { @@ -213,20 +255,20 @@ pub fn generate(component: Component, config: &Config) -> Result<()> { } => { println!( "{}", - controller::generate::(&rrgen, &name, &actions, &kind)? + controller::generate(&rrgen, &name, &actions, &kind, appinfo)? ); } Component::Task { name } => { - let vars = json!({"name": name, "pkg_name": H::app_name()}); + let vars = json!({"name": name, "pkg_name": appinfo.app_name}); rrgen.generate(TASK_T, &vars)?; rrgen.generate(TASK_TEST_T, &vars)?; } Component::Scheduler {} => { - let vars = json!({"pkg_name": H::app_name()}); + let vars = json!({"pkg_name": appinfo.app_name}); rrgen.generate(SCHEDULER_T, &vars)?; } Component::Worker { name } => { - let vars = json!({"name": name, "pkg_name": H::app_name()}); + let vars = json!({"name": name, "pkg_name": appinfo.app_name}); rrgen.generate(WORKER_T, &vars)?; rrgen.generate(WORKER_TEST_T, &vars)?; } @@ -237,43 +279,32 @@ pub fn generate(component: Component, config: &Config) -> Result<()> { rrgen.generate(MAILER_TEXT_T, &vars)?; rrgen.generate(MAILER_HTML_T, &vars)?; } - Component::Deployment {} => { + Component::Deployment { + fallback_file, + asset_folder, + host, + port, + } => { let deployment_kind = match std::env::var("LOCO_DEPLOYMENT_KIND") { - Ok(kind) => kind.parse::().map_err(|_e| { - errors::Error::Message(format!("deployment {kind} not supported")) - })?, + Ok(kind) => kind + .parse::() + .map_err(|_e| Error::Message(format!("deployment {kind} not supported")))?, Err(_err) => prompt_deployment_selection().map_err(Box::from)?, }; match deployment_kind { DeploymentKind::Docker => { - let copy_asset_folder = &config - .server - .middlewares - .static_assets - .clone() - .map(|a| a.folder.path) - .unwrap_or_default(); - - let fallback_file = &config - .server - .middlewares - .static_assets - .clone() - .map(|a| a.fallback) - .unwrap_or_default(); - let vars = json!({ - "pkg_name": H::app_name(), - "copy_asset_folder": copy_asset_folder, - "fallback_file": fallback_file + "pkg_name": appinfo.app_name, + "copy_asset_folder": asset_folder.unwrap_or_default(), + "fallback_file": fallback_file.unwrap_or_default() }); rrgen.generate(DEPLOYMENT_DOCKER_T, &vars)?; rrgen.generate(DEPLOYMENT_DOCKER_IGNORE_T, &vars)?; } DeploymentKind::Shuttle => { let vars = json!({ - "pkg_name": H::app_name(), + "pkg_name": appinfo.app_name, "shuttle_runtime_version": DEPLOYMENT_SHUTTLE_RUNTIME_VERSION, "with_db": cfg!(feature = "with-db") }); @@ -281,15 +312,11 @@ pub fn generate(component: Component, config: &Config) -> Result<()> { rrgen.generate(DEPLOYMENT_SHUTTLE_CONFIG_T, &vars)?; } DeploymentKind::Nginx => { - let host = &config - .server - .host - .replace("http://", "") - .replace("https://", ""); + let host = host.replace("http://", "").replace("https://", ""); let vars = json!({ - "pkg_name": H::app_name(), - "domain": &host, - "port": &config.server.port + "pkg_name": appinfo.app_name, + "domain": host, + "port": port }); rrgen.generate(DEPLOYMENT_NGINX_T, &vars)?; } @@ -321,7 +348,7 @@ fn prompt_deployment_selection() -> Result { .items(&options) .default(0) .interact() - .map_err(errors::Error::msg)?; + .map_err(Error::msg)?; Ok(DEPLOYMENT_OPTIONS[selection].1.clone()) } diff --git a/src/gen/mappings.json b/loco-gen/src/mappings.json similarity index 100% rename from src/gen/mappings.json rename to loco-gen/src/mappings.json diff --git a/src/gen/model.rs b/loco-gen/src/model.rs similarity index 55% rename from src/gen/model.rs rename to loco-gen/src/model.rs index e7d6fa08f..8b91ef241 100644 --- a/src/gen/model.rs +++ b/loco-gen/src/model.rs @@ -5,26 +5,27 @@ use duct::cmd; use rrgen::RRgen; use serde_json::json; -use crate::{app::Hooks, errors::Error, Result}; +use super::{Error, Result}; const MODEL_T: &str = include_str!("templates/model.t"); const MODEL_TEST_T: &str = include_str!("templates/model_test.t"); -use super::{collect_messages, MAPPINGS}; +use super::{collect_messages, AppInfo, MAPPINGS}; /// skipping some fields from the generated models. /// For example, the `created_at` and `updated_at` fields are automatically /// generated by the Loco app and should be given pub const IGNORE_FIELDS: &[&str] = &["created_at", "updated_at", "create_at", "update_at"]; -pub fn generate( +pub fn generate( rrgen: &RRgen, name: &str, is_link: bool, migration_only: bool, fields: &[(String, String)], + appinfo: &AppInfo, ) -> Result { - let pkg_name: &str = H::app_name(); + let pkg_name: &str = &appinfo.app_name; let ts = Utc::now(); let mut columns = Vec::new(); @@ -87,3 +88,72 @@ pub fn generate( let messages = collect_messages(vec![res1, res2]); Ok(messages) } + +#[cfg(test)] +mod tests { + use std::{env, process::Command}; + + use crate::{ + testutil::{self, assert_cargo_check, assert_file, assert_single_file_match}, + AppInfo, + }; + + fn with_new_app(app_name: &str, f: F) + where + F: FnOnce(), + { + testutil::with_temp_dir(|previous, current| { + let status = Command::new("loco") + .args([ + "new", + "-n", + app_name, + "-t", + "saas", + "--db", + "sqlite", + "--bg", + "async", + "--assets", + "serverside", + ]) + .env("STARTERS_LOCAL_PATH", previous.join("../")) + .status() + .expect("cannot run command"); + + assert!(status.success(), "Command failed: loco new -n {app_name}"); + env::set_current_dir(current.join(app_name)) + .expect("Failed to change directory to app"); + f(); // Execute the provided closure + }) + .expect("temp dir setup"); + } + + #[test] + fn test_can_generate_model() { + let rrgen = rrgen::RRgen::default(); + with_new_app("saas", || { + super::generate( + &rrgen, + "movies", + false, + true, + &[("title".to_string(), "string".to_string())], + &AppInfo { + app_name: "saas".to_string(), + }, + ) + .expect("generate"); + assert_file("migration/src/lib.rs", |content| { + content.assert_syntax(); + content.assert_regex_match("_movies::Migration"); + }); + let migration = assert_single_file_match("migration/src", ".*_movies.rs$"); + assert_file(migration.to_str().unwrap(), |content| { + content.assert_syntax(); + content.assert_regex_match("Title"); + }); + assert_cargo_check(); + }); + } +} diff --git a/src/gen/scaffold.rs b/loco-gen/src/scaffold.rs similarity index 94% rename from src/gen/scaffold.rs rename to loco-gen/src/scaffold.rs index e1e5b5093..d057f482b 100644 --- a/src/gen/scaffold.rs +++ b/loco-gen/src/scaffold.rs @@ -1,7 +1,7 @@ use rrgen::RRgen; use serde_json::json; -use crate::{app::Hooks, gen}; +use crate as gen; const API_CONTROLLER_SCAFFOLD_T: &str = include_str!("templates/scaffold/api/controller.t"); const API_CONTROLLER_TEST_T: &str = include_str!("templates/scaffold/api/test.t"); @@ -22,19 +22,19 @@ const HTML_VIEW_CREATE_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/ const HTML_VIEW_SHOW_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/view_show.t"); const HTML_VIEW_LIST_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/view_list.t"); -use super::{collect_messages, model, MAPPINGS}; -use crate::{errors::Error, Result}; +use super::{collect_messages, model, AppInfo, Error, Result, MAPPINGS}; -pub fn generate( +pub fn generate( rrgen: &RRgen, name: &str, fields: &[(String, String)], kind: &gen::ScaffoldKind, + appinfo: &AppInfo, ) -> Result { // - scaffold is never a link table // - never run with migration_only, because the controllers will refer to the // models. the models only arrive after migration and entities sync. - let model_messages = model::generate::(rrgen, name, false, false, fields)?; + let model_messages = model::generate(rrgen, name, false, false, fields, appinfo)?; let mut columns = Vec::new(); for (fname, ftype) in fields { @@ -56,7 +56,7 @@ pub fn generate( columns.push((fname.to_string(), schema_type.as_str(), ftype)); } } - let vars = json!({"name": name, "columns": columns, "pkg_name": H::app_name()}); + let vars = json!({"name": name, "columns": columns, "pkg_name": appinfo.app_name}); match kind { gen::ScaffoldKind::Api => { let res1 = rrgen.generate(API_CONTROLLER_SCAFFOLD_T, &vars)?; diff --git a/src/gen/templates/controller.t b/loco-gen/src/templates/controller.t similarity index 100% rename from src/gen/templates/controller.t rename to loco-gen/src/templates/controller.t diff --git a/src/gen/templates/controller/api/controller.t b/loco-gen/src/templates/controller/api/controller.t similarity index 100% rename from src/gen/templates/controller/api/controller.t rename to loco-gen/src/templates/controller/api/controller.t diff --git a/src/gen/templates/controller/api/test.t b/loco-gen/src/templates/controller/api/test.t similarity index 100% rename from src/gen/templates/controller/api/test.t rename to loco-gen/src/templates/controller/api/test.t diff --git a/src/gen/templates/controller/html/controller.t b/loco-gen/src/templates/controller/html/controller.t similarity index 100% rename from src/gen/templates/controller/html/controller.t rename to loco-gen/src/templates/controller/html/controller.t diff --git a/src/gen/templates/controller/html/view.t b/loco-gen/src/templates/controller/html/view.t similarity index 100% rename from src/gen/templates/controller/html/view.t rename to loco-gen/src/templates/controller/html/view.t diff --git a/src/gen/templates/controller/htmx/controller.t b/loco-gen/src/templates/controller/htmx/controller.t similarity index 100% rename from src/gen/templates/controller/htmx/controller.t rename to loco-gen/src/templates/controller/htmx/controller.t diff --git a/src/gen/templates/controller/htmx/view.t b/loco-gen/src/templates/controller/htmx/view.t similarity index 100% rename from src/gen/templates/controller/htmx/view.t rename to loco-gen/src/templates/controller/htmx/view.t diff --git a/src/gen/templates/deployment_docker.t b/loco-gen/src/templates/deployment_docker.t similarity index 100% rename from src/gen/templates/deployment_docker.t rename to loco-gen/src/templates/deployment_docker.t diff --git a/src/gen/templates/deployment_docker_ignore.t b/loco-gen/src/templates/deployment_docker_ignore.t similarity index 100% rename from src/gen/templates/deployment_docker_ignore.t rename to loco-gen/src/templates/deployment_docker_ignore.t diff --git a/src/gen/templates/deployment_nginx.t b/loco-gen/src/templates/deployment_nginx.t similarity index 100% rename from src/gen/templates/deployment_nginx.t rename to loco-gen/src/templates/deployment_nginx.t diff --git a/src/gen/templates/deployment_shuttle.t b/loco-gen/src/templates/deployment_shuttle.t similarity index 100% rename from src/gen/templates/deployment_shuttle.t rename to loco-gen/src/templates/deployment_shuttle.t diff --git a/src/gen/templates/deployment_shuttle_config.t b/loco-gen/src/templates/deployment_shuttle_config.t similarity index 100% rename from src/gen/templates/deployment_shuttle_config.t rename to loco-gen/src/templates/deployment_shuttle_config.t diff --git a/src/gen/templates/mailer.t b/loco-gen/src/templates/mailer.t similarity index 100% rename from src/gen/templates/mailer.t rename to loco-gen/src/templates/mailer.t diff --git a/src/gen/templates/mailer_html.t b/loco-gen/src/templates/mailer_html.t similarity index 100% rename from src/gen/templates/mailer_html.t rename to loco-gen/src/templates/mailer_html.t diff --git a/src/gen/templates/mailer_sub.t b/loco-gen/src/templates/mailer_sub.t similarity index 100% rename from src/gen/templates/mailer_sub.t rename to loco-gen/src/templates/mailer_sub.t diff --git a/src/gen/templates/mailer_text.t b/loco-gen/src/templates/mailer_text.t similarity index 100% rename from src/gen/templates/mailer_text.t rename to loco-gen/src/templates/mailer_text.t diff --git a/src/gen/templates/migration.t b/loco-gen/src/templates/migration.t similarity index 100% rename from src/gen/templates/migration.t rename to loco-gen/src/templates/migration.t diff --git a/src/gen/templates/model.t b/loco-gen/src/templates/model.t similarity index 100% rename from src/gen/templates/model.t rename to loco-gen/src/templates/model.t diff --git a/src/gen/templates/model_test.t b/loco-gen/src/templates/model_test.t similarity index 100% rename from src/gen/templates/model_test.t rename to loco-gen/src/templates/model_test.t diff --git a/src/gen/templates/request_test.t b/loco-gen/src/templates/request_test.t similarity index 100% rename from src/gen/templates/request_test.t rename to loco-gen/src/templates/request_test.t diff --git a/src/gen/templates/scaffold/api/controller.t b/loco-gen/src/templates/scaffold/api/controller.t similarity index 100% rename from src/gen/templates/scaffold/api/controller.t rename to loco-gen/src/templates/scaffold/api/controller.t diff --git a/src/gen/templates/scaffold/api/test.t b/loco-gen/src/templates/scaffold/api/test.t similarity index 100% rename from src/gen/templates/scaffold/api/test.t rename to loco-gen/src/templates/scaffold/api/test.t diff --git a/src/gen/templates/scaffold/html/base.t b/loco-gen/src/templates/scaffold/html/base.t similarity index 100% rename from src/gen/templates/scaffold/html/base.t rename to loco-gen/src/templates/scaffold/html/base.t diff --git a/src/gen/templates/scaffold/html/controller.t b/loco-gen/src/templates/scaffold/html/controller.t similarity index 100% rename from src/gen/templates/scaffold/html/controller.t rename to loco-gen/src/templates/scaffold/html/controller.t diff --git a/src/gen/templates/scaffold/html/view.t b/loco-gen/src/templates/scaffold/html/view.t similarity index 100% rename from src/gen/templates/scaffold/html/view.t rename to loco-gen/src/templates/scaffold/html/view.t diff --git a/src/gen/templates/scaffold/html/view_create.t b/loco-gen/src/templates/scaffold/html/view_create.t similarity index 100% rename from src/gen/templates/scaffold/html/view_create.t rename to loco-gen/src/templates/scaffold/html/view_create.t diff --git a/src/gen/templates/scaffold/html/view_edit.t b/loco-gen/src/templates/scaffold/html/view_edit.t similarity index 100% rename from src/gen/templates/scaffold/html/view_edit.t rename to loco-gen/src/templates/scaffold/html/view_edit.t diff --git a/src/gen/templates/scaffold/html/view_list.t b/loco-gen/src/templates/scaffold/html/view_list.t similarity index 100% rename from src/gen/templates/scaffold/html/view_list.t rename to loco-gen/src/templates/scaffold/html/view_list.t diff --git a/src/gen/templates/scaffold/html/view_show.t b/loco-gen/src/templates/scaffold/html/view_show.t similarity index 100% rename from src/gen/templates/scaffold/html/view_show.t rename to loco-gen/src/templates/scaffold/html/view_show.t diff --git a/src/gen/templates/scaffold/htmx/base.t b/loco-gen/src/templates/scaffold/htmx/base.t similarity index 100% rename from src/gen/templates/scaffold/htmx/base.t rename to loco-gen/src/templates/scaffold/htmx/base.t diff --git a/src/gen/templates/scaffold/htmx/controller.t b/loco-gen/src/templates/scaffold/htmx/controller.t similarity index 100% rename from src/gen/templates/scaffold/htmx/controller.t rename to loco-gen/src/templates/scaffold/htmx/controller.t diff --git a/src/gen/templates/scaffold/htmx/view.t b/loco-gen/src/templates/scaffold/htmx/view.t similarity index 100% rename from src/gen/templates/scaffold/htmx/view.t rename to loco-gen/src/templates/scaffold/htmx/view.t diff --git a/src/gen/templates/scaffold/htmx/view_create.t b/loco-gen/src/templates/scaffold/htmx/view_create.t similarity index 100% rename from src/gen/templates/scaffold/htmx/view_create.t rename to loco-gen/src/templates/scaffold/htmx/view_create.t diff --git a/src/gen/templates/scaffold/htmx/view_edit.t b/loco-gen/src/templates/scaffold/htmx/view_edit.t similarity index 100% rename from src/gen/templates/scaffold/htmx/view_edit.t rename to loco-gen/src/templates/scaffold/htmx/view_edit.t diff --git a/src/gen/templates/scaffold/htmx/view_list.t b/loco-gen/src/templates/scaffold/htmx/view_list.t similarity index 100% rename from src/gen/templates/scaffold/htmx/view_list.t rename to loco-gen/src/templates/scaffold/htmx/view_list.t diff --git a/src/gen/templates/scaffold/htmx/view_show.t b/loco-gen/src/templates/scaffold/htmx/view_show.t similarity index 100% rename from src/gen/templates/scaffold/htmx/view_show.t rename to loco-gen/src/templates/scaffold/htmx/view_show.t diff --git a/src/gen/templates/scheduler.t b/loco-gen/src/templates/scheduler.t similarity index 100% rename from src/gen/templates/scheduler.t rename to loco-gen/src/templates/scheduler.t diff --git a/src/gen/templates/task.t b/loco-gen/src/templates/task.t similarity index 100% rename from src/gen/templates/task.t rename to loco-gen/src/templates/task.t diff --git a/src/gen/templates/task_test.t b/loco-gen/src/templates/task_test.t similarity index 100% rename from src/gen/templates/task_test.t rename to loco-gen/src/templates/task_test.t diff --git a/src/gen/templates/worker.t b/loco-gen/src/templates/worker.t similarity index 100% rename from src/gen/templates/worker.t rename to loco-gen/src/templates/worker.t diff --git a/src/gen/templates/worker_test.t b/loco-gen/src/templates/worker_test.t similarity index 100% rename from src/gen/templates/worker_test.t rename to loco-gen/src/templates/worker_test.t diff --git a/loco-gen/src/testutil.rs b/loco-gen/src/testutil.rs new file mode 100644 index 000000000..44b2a1e77 --- /dev/null +++ b/loco-gen/src/testutil.rs @@ -0,0 +1,265 @@ +// +// generator test toolkit +// to be extracted to a library later. +// +use std::{ + env, + error::Error, + fs, + path::{Path, PathBuf}, + process::Command, +}; + +use regex::Regex; +use tempfile::tempdir; + +// Define the custom struct to encapsulate file content +pub struct FileContent { + content: String, +} + +impl FileContent { + // Method to load content from a file into the struct + pub fn from_file(file_path: &str) -> Result> { + let content = fs::read_to_string(file_path)?; + Ok(Self { content }) + } + + // Method to check that the content contains a specific string + pub fn check_contains(&self, pattern: &str) -> Result<(), Box> { + if self.content.contains(pattern) { + Ok(()) + } else { + Err(Box::from(format!("Content does not contain '{pattern}'"))) + } + } + + // Assert method for check_contains + pub fn assert_contains(&self, pattern: &str) { + self.check_contains(pattern) + .unwrap_or_else(|e| panic!("{}", e)); + } + + // Method to check that the content matches a regular expression + pub fn check_regex_match(&self, pattern: &str) -> Result<(), Box> { + let re = Regex::new(pattern)?; + if re.is_match(&self.content) { + Ok(()) + } else { + Err(Box::from(format!( + "Content does not match regex '{pattern}'" + ))) + } + } + + // Assert method for check_regex_match + pub fn assert_regex_match(&self, pattern: &str) { + self.check_regex_match(pattern) + .unwrap_or_else(|e| panic!("{}", e)); + } + + // Method to check that the content does not contain a specific string + pub fn check_not_contains(&self, pattern: &str) -> Result<(), Box> { + #[allow(clippy::if_not_else)] + if !self.content.contains(pattern) { + Ok(()) + } else { + Err(Box::from(format!("Content should not contain '{pattern}'"))) + } + } + + // Assert method for check_not_contains + pub fn assert_not_contains(&self, pattern: &str) { + self.check_not_contains(pattern) + .unwrap_or_else(|e| panic!("{}", e)); + } + + // Method to check the length of the content + pub fn check_length(&self, expected_length: usize) -> Result<(), Box> { + if self.content.len() == expected_length { + Ok(()) + } else { + Err(Box::from(format!( + "Content length is {}, expected {}", + self.content.len(), + expected_length + ))) + } + } + + // Assert method for check_length + pub fn assert_length(&self, expected_length: usize) { + self.check_length(expected_length) + .unwrap_or_else(|e| panic!("{}", e)); + } + + // Method to check the syntax using rustfmt without creating a temp file + pub fn check_syntax(&self) -> Result<(), Box> { + // Parse the file using `syn` to check for valid Rust syntax + match syn::parse_file(&self.content) { + Ok(_) => Ok(()), + Err(err) => Err(Box::from(format!("Syntax error: {err}"))), + } + } + + // Assert method for check_syntax + pub fn assert_syntax(&self) { + self.check_syntax().unwrap_or_else(|e| panic!("{}", e)); + } +} + +// Function that loads the file and applies the provided closure for assertions +pub fn check_file(file_path: &str, assertions: F) -> Result<(), Box> +where + F: Fn(&FileContent) -> Result<(), Box>, +{ + let content = FileContent::from_file(file_path)?; + assertions(&content)?; + Ok(()) +} + +// Assert function for checking the file with a closure for custom assertions +pub fn assert_file(file_path: &str, assertions: F) +where + F: Fn(&FileContent), +{ + check_file(file_path, |content| { + assertions(content); + Ok(()) + }) + .unwrap_or_else(|e| panic!("{}", e)); +} + +pub fn check_no_warnings() -> Result<(), Box> { + let output = Command::new("cargo") + .arg("check") + .arg("--message-format=json") + .output()?; + + let stdout = String::from_utf8(output.stdout)?; + if stdout.contains("warning:") { + Err(Box::from("Compilation produced warnings")) + } else { + Ok(()) + } +} + +pub fn assert_no_warnings() { + check_no_warnings().unwrap_or_else(|e| panic!("{}", e)); +} + +pub fn check_cargo_check() -> Result<(), Box> { + let output = Command::new("cargo").arg("check").output()?; // Execute the command and get the output + + // Check if cargo check was successful + if output.status.success() { + Ok(()) + } else { + // Capture and return the error output if the command failed + let error_message = String::from_utf8_lossy(&output.stderr); + Err(Box::from(format!("cargo check failed: {error_message}"))) + } +} + +pub fn assert_cargo_check() { + check_cargo_check().unwrap_or_else(|e| panic!("{}", e)); +} + +pub fn check_file_not_exists(file_path: &str) -> Result<(), Box> { + if std::path::Path::new(file_path).exists() { + Err(Box::from(format!("File {file_path} should not exist"))) + } else { + Ok(()) + } +} + +pub fn assert_file_not_exists(file_path: &str) { + check_file_not_exists(file_path).unwrap_or_else(|e| panic!("{}", e)); +} + +pub fn check_file_exists(file_path: &str) -> Result<(), Box> { + if std::path::Path::new(file_path).exists() { + Ok(()) + } else { + Err(Box::from(format!("File {file_path} does not exist"))) + } +} + +pub fn assert_file_exists(file_path: &str) { + check_file_exists(file_path).unwrap_or_else(|e| panic!("{}", e)); +} + +pub fn check_dir_exists(dir_path: &str) -> Result<(), Box> { + if std::path::Path::new(dir_path).is_dir() { + Ok(()) + } else { + Err(Box::from(format!("Directory {dir_path} does not exist"))) + } +} + +pub fn assert_dir_exists(dir_path: &str) { + check_dir_exists(dir_path).unwrap_or_else(|e| panic!("{}", e)); +} + +/// Checks if there exists exactly one file in the given directory whose name +/// matches the provided regex pattern. +pub fn check_single_file_match>( + dir: P, + pattern: &str, +) -> Result> { + // Compile the provided regex pattern + let re = Regex::new(pattern)?; + + // Filter files that match the regex + let matched_files: Vec = fs::read_dir(dir)? + .filter_map(std::result::Result::ok) + .filter_map(|entry| { + let path = entry.path(); + + #[allow(clippy::option_if_let_else)] + if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) { + if re.is_match(file_name) { + Some(path) // Return the path if the regex matches + } else { + None + } + } else { + None + } + }) + .collect(); + + // Ensure that there is exactly one match + match matched_files.len() { + 0 => Err(Box::from("No file found matching the given pattern.")), + 1 => Ok(matched_files.into_iter().next().unwrap()), /* Return the single matching file's */ + // path + _ => Err(Box::from("More than one file matches the given pattern.")), + } +} + +pub fn assert_single_file_match>(dir: P, pattern: &str) -> PathBuf { + check_single_file_match(dir, pattern).unwrap_or_else(|e| panic!("{}", e)) +} + +pub fn with_temp_dir(f: F) -> Result<(), Box> +where + F: FnOnce(&Path, &Path), +{ + let previous = env::current_dir()?; // Get the current directory + println!("Current directory: {previous:?}"); + + let temp_dir = tempdir()?; // Create a temporary directory + let current = temp_dir.path(); + + println!("Temporary directory: {current:?}"); + env::set_current_dir(current)?; // Set the current directory to the temp directory + + // Use catch_unwind to handle panics gracefully + f(previous.as_path(), current); // Execute the provided closure + + // Restore the original directory + env::set_current_dir(previous)?; + + Ok(()) +} diff --git a/src/cli.rs b/src/cli.rs index 28a6b80c8..731bb8435 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -28,6 +28,7 @@ use std::path::PathBuf; use clap::{ArgAction, Parser, Subcommand}; use duct::cmd; +use loco_gen::{Component, ScaffoldKind}; use crate::{ app::{AppContext, Hooks}, @@ -35,8 +36,8 @@ use crate::{ create_app, create_context, list_endpoints, list_middlewares, run_scheduler, run_task, start, RunDbCommand, ServeParams, StartMode, }, + config::Config, environment::{resolve_from_env, Environment, DEFAULT_ENVIRONMENT}, - gen::{self, Component, ScaffoldKind}, logger, task, Error, }; #[derive(Parser)] @@ -190,7 +191,7 @@ enum ComponentArg { /// The kind of scaffold to generate #[clap(short, long, value_enum, group = "scaffold_kind_group")] - kind: Option, + kind: Option, /// Use HTMX scaffold #[clap(long, group = "scaffold_kind_group")] @@ -214,7 +215,7 @@ enum ComponentArg { /// The kind of controller actions to generate #[clap(short, long, value_enum, group = "scaffold_kind_group")] - kind: Option, + kind: Option, /// Use HTMX controller actions #[clap(long, group = "scaffold_kind_group")] @@ -249,26 +250,25 @@ enum ComponentArg { Deployment {}, } -impl TryFrom for Component { - type Error = crate::Error; - fn try_from(value: ComponentArg) -> Result { - match value { +impl ComponentArg { + fn into_gen_component(self, config: &Config) -> crate::Result { + match self { #[cfg(feature = "with-db")] - ComponentArg::Model { + Self::Model { name, link, migration_only, fields, - } => Ok(Self::Model { + } => Ok(Component::Model { name, link, migration_only, fields, }), #[cfg(feature = "with-db")] - ComponentArg::Migration { name } => Ok(Self::Migration { name }), + Self::Migration { name } => Ok(Component::Migration { name }), #[cfg(feature = "with-db")] - ComponentArg::Scaffold { + Self::Scaffold { name, fields, kind, @@ -290,9 +290,9 @@ impl TryFrom for Component { )); }; - Ok(Self::Scaffold { name, fields, kind }) + Ok(Component::Scaffold { name, fields, kind }) } - ComponentArg::Controller { + Self::Controller { name, actions, kind, @@ -314,17 +314,38 @@ impl TryFrom for Component { )); }; - Ok(Self::Controller { + Ok(Component::Controller { name, actions, kind, }) } - ComponentArg::Task { name } => Ok(Self::Task { name }), - ComponentArg::Scheduler {} => Ok(Self::Scheduler {}), - ComponentArg::Worker { name } => Ok(Self::Worker { name }), - ComponentArg::Mailer { name } => Ok(Self::Mailer { name }), - ComponentArg::Deployment {} => Ok(Self::Deployment {}), + Self::Task { name } => Ok(Component::Task { name }), + Self::Scheduler {} => Ok(Component::Scheduler {}), + Self::Worker { name } => Ok(Component::Worker { name }), + Self::Mailer { name } => Ok(Component::Mailer { name }), + Self::Deployment {} => { + let copy_asset_folder = &config + .server + .middlewares + .static_assets + .clone() + .map(|a| a.folder.path); + + let fallback_file = &config + .server + .middlewares + .static_assets + .clone() + .map(|a| a.fallback); + + Ok(Component::Deployment { + asset_folder: copy_asset_folder.clone(), + fallback_file: fallback_file.clone(), + host: config.server.host.clone(), + port: config.server.port, + }) + } } } } @@ -427,6 +448,7 @@ pub async fn playground() -> crate::Result { #[allow(clippy::cognitive_complexity)] pub async fn main() -> crate::Result<()> { use colored::Colorize; + use loco_gen::AppInfo; let cli: Cli = Cli::parse(); let environment: Environment = cli.environment.unwrap_or_else(resolve_from_env).into(); @@ -511,7 +533,12 @@ pub async fn main() -> crate::Result<()> { run_scheduler::(&app_context, config.as_ref(), name, tag, list).await?; } Commands::Generate { component } => { - gen::generate::(component.try_into()?, &config)?; + loco_gen::generate( + component.into_gen_component(&config)?, + &AppInfo { + app_name: H::app_name().to_string(), + }, + )?; } Commands::Doctor { config: config_arg } => { if config_arg { @@ -562,6 +589,7 @@ pub async fn main() -> crate::Result<()> { #[cfg(not(feature = "with-db"))] pub async fn main() -> crate::Result<()> { use colored::Colorize; + use loco_gen::AppInfo; let cli = Cli::parse(); let environment: Environment = cli.environment.unwrap_or_else(resolve_from_env).into(); @@ -639,7 +667,12 @@ pub async fn main() -> crate::Result<()> { run_scheduler::(&app_context, config.as_ref(), name, tag, list).await?; } Commands::Generate { component } => { - gen::generate::(component.try_into()?, &config)?; + gen::generate( + component.into_gen_component(&config)?, + &AppInfo { + app_name: H::app_name().to_string(), + }, + )?; } Commands::Version {} => { println!("{}", H::app_version(),); diff --git a/src/errors.rs b/src/errors.rs index 334e3e0f8..58fe9770e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -84,9 +84,6 @@ pub enum Error { #[error(transparent)] DB(#[from] sea_orm::DbErr), - #[error(transparent)] - RRgen(#[from] rrgen::Error), - #[error(transparent)] ParseAddress(#[from] AddressError), @@ -145,6 +142,9 @@ pub enum Error { #[error(transparent)] Cache(#[from] crate::cache::CacheError), + #[error(transparent)] + Generators(#[from] loco_gen::Error), + #[error(transparent)] Any(#[from] Box), } diff --git a/src/lib.rs b/src/lib.rs index 5b3568b78..ce662c702 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,6 @@ pub mod config; pub mod controller; pub mod environment; pub mod errors; -mod gen; pub mod hash; mod logger; pub mod mailer; From e9a87faffd86cd11feb1e3e9f3fe2d497845ea79 Mon Sep 17 00:00:00 2001 From: "Dotan J. Nahum" Date: Tue, 22 Oct 2024 10:07:29 +0300 Subject: [PATCH 2/5] feat: loco doctor now checks min SeaORM CLI version (#890) * feat: loco doctor now checks min SeaORM CLI version * align SeaORM version --- .github/workflows/ci-loco-gen.yml | 2 +- CHANGELOG.md | 3 +- Cargo.toml | 3 +- examples/demo/Cargo.lock | 57 +++++++++----------------- examples/demo/migration/Cargo.toml | 2 +- src/cli.rs | 2 +- src/db.rs | 2 +- src/doctor.rs | 64 ++++++++++++++++++++++++------ 8 files changed, 79 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci-loco-gen.yml b/.github/workflows/ci-loco-gen.yml index 4ea15d376..940abbf5d 100644 --- a/.github/workflows/ci-loco-gen.yml +++ b/.github/workflows/ci-loco-gen.yml @@ -80,4 +80,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all-features --all + args: --all-features diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c0b43b70..d16c8fd4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleased -* **BREAKING** Improved migration generator. If you have an existing migration project, add the following comment indicator to the top of the `vec` statement and right below the opening bracked like so: +* `cargo loco doctor` now checks for minimal required SeaORM CLI version +* **BREAKING** Improved migration generator. If you have an existing migration project, add the following comment indicator to the top of the `vec` statement and right below the opening bracked like so in `migration/src/lib.rs`: ```rust fn migrations() -> Vec> { vec![ diff --git a/Cargo.toml b/Cargo.toml index c72e26bae..c4e2a073f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ clap = { version = "4.4.7", features = ["derive"], optional = true } colored = "2" -sea-orm = { version = "1.0.0", features = [ +sea-orm = { version = "1.1.0", features = [ "sqlx-postgres", # `DATABASE_DRIVER` feature "sqlx-sqlite", "runtime-tokio-rustls", @@ -101,6 +101,7 @@ hyper = "1.1" mime = "0.3" bytes = "1.1" ipnetwork = "0.20.0" +semver = "1" axum-test = { version = "16.1.0", optional = true } diff --git a/examples/demo/Cargo.lock b/examples/demo/Cargo.lock index 3d5d8fa95..e3862cde8 100644 --- a/examples/demo/Cargo.lock +++ b/examples/demo/Cargo.lock @@ -946,38 +946,6 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.23", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cc" version = "1.1.30" @@ -2983,6 +2951,23 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "loco-gen" +version = "0.11.0" +dependencies = [ + "chrono", + "clap", + "dialoguer", + "duct", + "lazy_static", + "regex", + "rrgen", + "serde", + "serde_json", + "thiserror", + "tracing", +] + [[package]] name = "loco-rs" version = "0.11.0" @@ -2996,12 +2981,10 @@ dependencies = [ "bb8", "byte-unit", "bytes", - "cargo_metadata", "cfg-if", "chrono", "clap", "colored", - "dialoguer", "duct", "duct_sh", "english-to-cron", @@ -3014,15 +2997,16 @@ dependencies = [ "jsonwebtoken", "lazy_static", "lettre", + "loco-gen", "mime", "moka", "object_store", "rand", "regex", - "rrgen", "rusty-sidekiq", "sea-orm", "sea-orm-migration", + "semver 1.0.23", "serde", "serde_json", "serde_variant", @@ -4793,9 +4777,6 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] [[package]] name = "semver-parser" diff --git a/examples/demo/migration/Cargo.toml b/examples/demo/migration/Cargo.toml index 4726bb267..fd31e14f5 100644 --- a/examples/demo/migration/Cargo.toml +++ b/examples/demo/migration/Cargo.toml @@ -13,7 +13,7 @@ async-std = { version = "1", features = ["attributes", "tokio1"] } loco-rs = { path = "../../../", version = "*" } [dependencies.sea-orm-migration] -version = "1.0.0" +version = "1.1.0" features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. diff --git a/src/cli.rs b/src/cli.rs index 731bb8435..550d17b64 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -546,7 +546,7 @@ pub async fn main() -> crate::Result<()> { println!("Environment: {}", &environment); } else { let mut should_exit = false; - for (_, check) in doctor::run_all(&config).await { + for (_, check) in doctor::run_all(&config).await? { if !should_exit && !check.valid() { should_exit = true; } diff --git a/src/db.rs b/src/db.rs index 19fe38564..fcbd3faad 100644 --- a/src/db.rs +++ b/src/db.rs @@ -268,7 +268,7 @@ where /// /// Returns a [`AppResult`] if an error occurs during generate model entity. pub async fn entities(ctx: &AppContext) -> AppResult { - doctor::check_seaorm_cli().to_result()?; + doctor::check_seaorm_cli()?.to_result()?; doctor::check_db(&ctx.config.database).await.to_result()?; let out = cmd!( diff --git a/src/doctor.rs b/src/doctor.rs index cb5e44fe4..78019b332 100644 --- a/src/doctor.rs +++ b/src/doctor.rs @@ -1,5 +1,8 @@ use std::{collections::BTreeMap, process::Command}; +use regex::Regex; +use semver::Version; + use crate::{ bgworker, config::{self, Config, Database}, @@ -88,9 +91,11 @@ impl std::fmt::Display for Check { } /// Runs checks for all configured resources. -pub async fn run_all(config: &Config) -> BTreeMap { +/// # Errors +/// Error when one of the checks fail +pub async fn run_all(config: &Config) -> Result> { let mut checks = BTreeMap::from([ - (Resource::SeaOrmCLI, check_seaorm_cli()), + (Resource::SeaOrmCLI, check_seaorm_cli()?), (Resource::Database, check_db(&config.database).await), ]); @@ -98,7 +103,7 @@ pub async fn run_all(config: &Config) -> BTreeMap { checks.insert(Resource::Redis, check_queue(config).await); } - checks + Ok(checks) } /// Checks the database connection. @@ -155,19 +160,54 @@ pub async fn check_queue(config: &Config) -> Check { } } +const MIN_SEAORMCLI_VER: &str = "1.1.0"; /// Checks the presence and version of `SeaORM` CLI. -#[must_use] -pub fn check_seaorm_cli() -> Check { +/// # Panics +/// On illegal regex +/// # Errors +/// Fails when cannot check version +pub fn check_seaorm_cli() -> Result { match Command::new("sea-orm-cli").arg("--version").output() { - Ok(_) => Check { - status: CheckStatus::Ok, - message: SEAORM_INSTALLED.to_string(), - description: None, - }, - Err(_) => Check { + Ok(out) => { + let input = String::from_utf8_lossy(&out.stdout); + // Extract the version from the input string + let re = Regex::new(r"(\d+\.\d+\.\d+)").unwrap(); + + let version_str = re + .captures(&input) + .and_then(|caps| caps.get(0)) + .map(|m| m.as_str()) + .ok_or("SeaORM CLI version not found") + .map_err(Box::from)?; + + // Parse the extracted version using semver + let version = Version::parse(version_str).map_err(Box::from)?; + + // Parse the minimum version for comparison + let min_version = Version::parse(MIN_SEAORMCLI_VER).map_err(Box::from)?; + + // Compare the extracted version with the minimum version + if version >= min_version { + Ok(Check { + status: CheckStatus::Ok, + message: SEAORM_INSTALLED.to_string(), + description: None, + }) + } else { + Ok(Check { + status: CheckStatus::NotOk, + message: format!( + "SeaORM CLI minimal version is `{min_version}` (you have `{version}`). \ + Run `cargo install sea-orm-cli` to update." + ), + description: Some(SEAORM_NOT_FIX.to_string()), + }) + } + } + Err(_) => Ok(Check { status: CheckStatus::NotOk, message: SEAORM_NOT_INSTALLED.to_string(), description: Some(SEAORM_NOT_FIX.to_string()), - }, + }), } } From c1cf58d2bceb026187fdde87f280ad693c709a27 Mon Sep 17 00:00:00 2001 From: Darric Heng Date: Tue, 22 Oct 2024 15:08:52 +0800 Subject: [PATCH 3/5] fix: typos of did't (#888) Co-authored-by: Dotan J. Nahum --- src/controller/format.rs | 10 +++++----- src/controller/mod.rs | 2 +- src/mailer/email_sender.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/controller/format.rs b/src/controller/format.rs index 230e4523f..113222767 100644 --- a/src/controller/format.rs +++ b/src/controller/format.rs @@ -55,7 +55,7 @@ use crate::{ /// /// # Errors /// -/// Currently this function did't return any error. this is for feature +/// Currently this function doesn't return any error. this is for feature /// functionality pub fn empty() -> Result { Ok(().into_response()) @@ -76,7 +76,7 @@ pub fn empty() -> Result { /// /// # Errors /// -/// Currently this function did't return any error. this is for feature +/// Currently this function doesn't return any error. this is for feature /// functionality pub fn text(t: &str) -> Result { Ok(t.to_string().into_response()) @@ -105,7 +105,7 @@ pub fn text(t: &str) -> Result { /// /// # Errors /// -/// Currently this function did't return any error. this is for feature +/// Currently this function doesn't return any error. this is for feature /// functionality pub fn json(t: T) -> Result { Ok(Json(t).into_response()) @@ -134,7 +134,7 @@ pub fn empty_json() -> Result { /// /// # Errors /// -/// Currently this function did't return any error. this is for feature +/// Currently this function doesn't return any error. this is for feature /// functionality pub fn html(content: &str) -> Result { Ok(Html(content.to_string()).into_response()) @@ -154,7 +154,7 @@ pub fn html(content: &str) -> Result { /// /// # Errors /// -/// Currently this function did't return any error. this is for feature +/// Currently this function doesn't return any error. this is for feature /// functionality pub fn redirect(to: &str) -> Result { Ok(Redirect::to(to).into_response()) diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 66ead17a3..21fb9e797 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -136,7 +136,7 @@ pub fn bad_request, U>(msg: T) -> Result { /// return not found status code /// /// # Errors -/// Currently this function did't return any error. this is for feature +/// Currently this function doesn't return any error. this is for feature /// functionality pub fn not_found() -> Result { Err(Error::NotFound) diff --git a/src/mailer/email_sender.rs b/src/mailer/email_sender.rs index bcfb9a082..61fe0474b 100644 --- a/src/mailer/email_sender.rs +++ b/src/mailer/email_sender.rs @@ -93,7 +93,7 @@ impl EmailSender { /// /// # Errors /// - /// When email did't send successfully or has an error to build the message + /// When email doesn't send successfully or has an error to build the message pub async fn mail(&self, email: &Email) -> Result<()> { let content = MultiPart::alternative_plain_html(email.text.clone(), email.html.clone()); let mut builder = Message::builder() From fee55ad98d97d02010beadf2f34f864e7810659b Mon Sep 17 00:00:00 2001 From: "Dotan J. Nahum" Date: Tue, 22 Oct 2024 11:48:46 +0300 Subject: [PATCH 4/5] bump: 0.11.1 (#891) --- Cargo.toml | 2 +- src/cli.rs | 2 +- starters/lightweight-service/Cargo.lock | 60 +++++++++---------------- starters/lightweight-service/Cargo.toml | 4 +- starters/rest-api/Cargo.lock | 59 +++++++++--------------- starters/rest-api/Cargo.toml | 4 +- starters/rest-api/migration/Cargo.toml | 2 +- starters/saas/Cargo.lock | 59 +++++++++--------------- starters/saas/Cargo.toml | 4 +- starters/saas/migration/Cargo.toml | 2 +- 10 files changed, 71 insertions(+), 127 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4e2a073f..69a854fd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" [package] name = "loco-rs" -version = "0.11.0" +version = "0.11.1" description = "The one-person framework for Rust" homepage = "https://loco.rs/" documentation = "https://docs.rs/loco-rs" diff --git a/src/cli.rs b/src/cli.rs index 550d17b64..f178d65e4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -667,7 +667,7 @@ pub async fn main() -> crate::Result<()> { run_scheduler::(&app_context, config.as_ref(), name, tag, list).await?; } Commands::Generate { component } => { - gen::generate( + loco_gen::generate( component.into_gen_component(&config)?, &AppInfo { app_name: H::app_name().to_string(), diff --git a/starters/lightweight-service/Cargo.lock b/starters/lightweight-service/Cargo.lock index 7e2d1bfa7..52a190a24 100644 --- a/starters/lightweight-service/Cargo.lock +++ b/starters/lightweight-service/Cargo.lock @@ -431,38 +431,6 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cc" version = "1.1.6" @@ -489,6 +457,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] @@ -1417,8 +1386,25 @@ dependencies = [ ] [[package]] -name = "loco-rs" +name = "loco-gen" version = "0.11.0" +dependencies = [ + "chrono", + "clap", + "dialoguer", + "duct", + "lazy_static", + "regex", + "rrgen", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "loco-rs" +version = "0.11.1" dependencies = [ "argon2", "async-trait", @@ -1428,12 +1414,10 @@ dependencies = [ "backtrace_printer", "byte-unit", "bytes", - "cargo_metadata", "cfg-if", "chrono", "clap", "colored", - "dialoguer", "duct", "duct_sh", "english-to-cron", @@ -1445,11 +1429,12 @@ dependencies = [ "ipnetwork", "lazy_static", "lettre", + "loco-gen", "mime", "object_store", "rand", "regex", - "rrgen", + "semver", "serde", "serde_json", "serde_variant", @@ -2206,9 +2191,6 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] [[package]] name = "serde" diff --git a/starters/lightweight-service/Cargo.toml b/starters/lightweight-service/Cargo.toml index e5ee0e1c7..3c1092c61 100644 --- a/starters/lightweight-service/Cargo.toml +++ b/starters/lightweight-service/Cargo.toml @@ -11,7 +11,7 @@ default-run = "loco_starter_template-cli" [dependencies] -loco-rs = { version = "0.11.0", default-features = false, features = ["cli"] } +loco-rs = { version = "0.11.1", default-features = false, features = ["cli"] } serde = "1" serde_json = "1" tokio = { version = "1.33.0", default-features = false, features = [ @@ -35,7 +35,7 @@ required-features = [] [dev-dependencies] serial_test = "3.1.1" rstest = "0.21.0" -loco-rs = { version = "0.11.0", default-features = false, features = [ +loco-rs = { version = "0.11.1", default-features = false, features = [ "testing", "cli", ] } diff --git a/starters/rest-api/Cargo.lock b/starters/rest-api/Cargo.lock index 6007062f0..ea3dfb416 100644 --- a/starters/rest-api/Cargo.lock +++ b/starters/rest-api/Cargo.lock @@ -763,38 +763,6 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cc" version = "1.1.6" @@ -2161,8 +2129,25 @@ dependencies = [ ] [[package]] -name = "loco-rs" +name = "loco-gen" version = "0.11.0" +dependencies = [ + "chrono", + "clap", + "dialoguer", + "duct", + "lazy_static", + "regex", + "rrgen", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "loco-rs" +version = "0.11.1" dependencies = [ "argon2", "async-trait", @@ -2173,12 +2158,10 @@ dependencies = [ "bb8", "byte-unit", "bytes", - "cargo_metadata", "cfg-if", "chrono", "clap", "colored", - "dialoguer", "duct", "duct_sh", "english-to-cron", @@ -2191,15 +2174,16 @@ dependencies = [ "jsonwebtoken", "lazy_static", "lettre", + "loco-gen", "mime", "moka", "object_store", "rand", "regex", - "rrgen", "rusty-sidekiq", "sea-orm", "sea-orm-migration", + "semver", "serde", "serde_json", "serde_variant", @@ -3576,9 +3560,6 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] [[package]] name = "serde" diff --git a/starters/rest-api/Cargo.toml b/starters/rest-api/Cargo.toml index a3ec29c8a..0d7d7737b 100644 --- a/starters/rest-api/Cargo.toml +++ b/starters/rest-api/Cargo.toml @@ -11,7 +11,7 @@ default-run = "loco_starter_template-cli" [dependencies] -loco-rs = { version = "0.11.0" } +loco-rs = { version = "0.11.1" } migration = { path = "migration" } serde = { version = "1", features = ["derive"] } @@ -46,5 +46,5 @@ required-features = [] [dev-dependencies] serial_test = "3.1.1" rstest = "0.21.0" -loco-rs = { version = "0.11.0", features = ["testing"] } +loco-rs = { version = "0.11.1", features = ["testing"] } insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } diff --git a/starters/rest-api/migration/Cargo.toml b/starters/rest-api/migration/Cargo.toml index 839562279..d614afb42 100644 --- a/starters/rest-api/migration/Cargo.toml +++ b/starters/rest-api/migration/Cargo.toml @@ -10,7 +10,7 @@ path = "src/lib.rs" [dependencies] async-std = { version = "1", features = ["attributes", "tokio1"] } -loco-rs = { version = "0.11.0" } +loco-rs = { version = "0.11.1" } [dependencies.sea-orm-migration] version = "1.1.0" diff --git a/starters/saas/Cargo.lock b/starters/saas/Cargo.lock index 8cee134f7..54ced07df 100644 --- a/starters/saas/Cargo.lock +++ b/starters/saas/Cargo.lock @@ -769,38 +769,6 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cc" version = "1.1.6" @@ -2296,8 +2264,25 @@ dependencies = [ ] [[package]] -name = "loco-rs" +name = "loco-gen" version = "0.11.0" +dependencies = [ + "chrono", + "clap", + "dialoguer", + "duct", + "lazy_static", + "regex", + "rrgen", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "loco-rs" +version = "0.11.1" dependencies = [ "argon2", "async-trait", @@ -2308,12 +2293,10 @@ dependencies = [ "bb8", "byte-unit", "bytes", - "cargo_metadata", "cfg-if", "chrono", "clap", "colored", - "dialoguer", "duct", "duct_sh", "english-to-cron", @@ -2326,15 +2309,16 @@ dependencies = [ "jsonwebtoken", "lazy_static", "lettre", + "loco-gen", "mime", "moka", "object_store", "rand", "regex", - "rrgen", "rusty-sidekiq", "sea-orm", "sea-orm-migration", + "semver", "serde", "serde_json", "serde_variant", @@ -3740,9 +3724,6 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] [[package]] name = "serde" diff --git a/starters/saas/Cargo.toml b/starters/saas/Cargo.toml index 8f63be914..8778191ab 100644 --- a/starters/saas/Cargo.toml +++ b/starters/saas/Cargo.toml @@ -11,7 +11,7 @@ default-run = "loco_starter_template-cli" [dependencies] -loco-rs = { version = "0.11.0" } +loco-rs = { version = "0.11.1" } migration = { path = "migration" } serde = { version = "1", features = ["derive"] } @@ -51,5 +51,5 @@ required-features = [] [dev-dependencies] serial_test = "3.1.1" rstest = "0.21.0" -loco-rs = { version = "0.11.0", features = ["testing"] } +loco-rs = { version = "0.11.1", features = ["testing"] } insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } diff --git a/starters/saas/migration/Cargo.toml b/starters/saas/migration/Cargo.toml index 839562279..d614afb42 100644 --- a/starters/saas/migration/Cargo.toml +++ b/starters/saas/migration/Cargo.toml @@ -10,7 +10,7 @@ path = "src/lib.rs" [dependencies] async-std = { version = "1", features = ["attributes", "tokio1"] } -loco-rs = { version = "0.11.0" } +loco-rs = { version = "0.11.1" } [dependencies.sea-orm-migration] version = "1.1.0" From 25d892120a13019cc3a2051a153273165871537d Mon Sep 17 00:00:00 2001 From: Dotan Nahum Date: Tue, 22 Oct 2024 11:49:29 +0300 Subject: [PATCH 5/5] xtask: bump version takes into account the subcrates --- xtask/src/bump_version.rs | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/xtask/src/bump_version.rs b/xtask/src/bump_version.rs index 1d20cf6b0..c55392cd1 100644 --- a/xtask/src/bump_version.rs +++ b/xtask/src/bump_version.rs @@ -40,8 +40,9 @@ impl BumpVersion { /// # Errors /// Returns an error when it could not update one of the resources. pub fn run(&self) -> Result<()> { - self.bump_loco_framework()?; - println!("Bump Loco lib updated successfully"); + self.bump_loco_framework(".")?; + self.bump_loco_framework("loco-gen")?; + self.bump_subcrates_version(&["loco-gen"])?; // change starters from fixed (v0.1.x) to local ("../../") in order // to test all starters against what is going to be released @@ -85,10 +86,12 @@ impl BumpVersion { /// # Errors /// Returns an error when it could not parse the loco Cargo.toml file or has /// an error updating the file. - fn bump_loco_framework(&self) -> Result<()> { + fn bump_loco_framework(&self, path: &str) -> Result<()> { + println!("bumping to `{}` on `{path}`", self.version); + let mut content = String::new(); - let cargo_toml_file = self.base_dir.join("Cargo.toml"); + let cargo_toml_file = self.base_dir.join(path).join("Cargo.toml"); fs::File::open(&cargo_toml_file)?.read_to_string(&mut content)?; if !REPLACE_LOCO_LIB_VERSION_.is_match(&content) { @@ -98,9 +101,10 @@ impl BumpVersion { }); } - let content = REPLACE_LOCO_LIB_VERSION_.replace(&content, |captures: ®ex::Captures| { - format!("{}{}", &captures["name"], self.version) - }); + let content = REPLACE_LOCO_LIB_VERSION_ + .replace(&content, |captures: ®ex::Captures<'_>| { + format!("{}{}", &captures["name"], self.version) + }); let mut modified_file = fs::File::create(cargo_toml_file)?; modified_file.write_all(content.as_bytes())?; @@ -108,6 +112,45 @@ impl BumpVersion { Ok(()) } + fn bump_subcrates_version(&self, crates: &[&str]) -> Result<()> { + let mut content = String::new(); + + let cargo_toml_file = self.base_dir.join("Cargo.toml"); + fs::File::open(&cargo_toml_file)?.read_to_string(&mut content)?; + + println!("in root package:"); + for subcrate in crates { + println!("bumping subcrate `{}` to `{}`", subcrate, self.version); + let re = Regex::new(&format!( + r#"{subcrate}\s*=\s*\{{\s*version\s*=\s*"[0-9]+\.[0-9]+\.[0-9]+",\s*path\s*=\s*"[^"]+"\s*\}}"#, + )) + .unwrap(); + + if !re.is_match(&content) { + return Err(Error::BumpVersion { + path: cargo_toml_file.clone(), + package: subcrate.to_string(), + }); + } + + // Replace the full version line with the new version, keeping the structure + // intact + content = re + .replace( + &content, + format!( + r#"{subcrate} = {{ version = "{}", path = "./{subcrate}" }}"#, + self.version + ), + ) + .to_string(); + } + + let mut modified_file = fs::File::create(cargo_toml_file)?; + modified_file.write_all(content.as_bytes())?; + Ok(()) + } + /// Update the dependencies of loco-rs in all starter projects to the given /// version. /// @@ -149,7 +192,7 @@ impl BumpVersion { }); } content = REPLACE_LOCO_PACKAGE_VERSION - .replace_all(&content, |_captures: ®ex::Captures| { + .replace_all(&content, |_captures: ®ex::Captures<'_>| { replace_with.to_string() }) .to_string();