From 694a13188755c2d2677f05d1c8b637b38ea2dba1 Mon Sep 17 00:00:00 2001 From: "Dotan J. Nahum" Date: Thu, 21 Nov 2024 09:36:55 +0200 Subject: [PATCH] os conditional starter (#999) * os conditional starter * align release process + test fixes * flaky test --- .github/workflows/loco-new.yml | 3 ++ loco-gen/.gitattributes | 1 + .../.cargo/{config.toml => config.toml.t} | 5 ++ loco-new/base_template/Cargo.toml.t | 2 + loco-new/setup.rhai | 11 +++- loco-new/src/bin/main.rs | 17 +++++-- loco-new/src/generator/mod.rs | 3 +- loco-new/src/lib.rs | 18 +++++++ loco-new/src/settings.rs | 7 ++- loco-new/tests/templates/db.rs | 1 + loco-new/tests/templates/module_name.rs | 4 +- loco-new/tests/wizard/new.rs | 50 +++++++++++++++--- src/controller/middleware/secure_headers.rs | 19 +++++-- ..._headers__tests__can_override_headers.snap | 5 +- xtask/src/ci.rs | 23 +++++---- xtask/src/versions.rs | 51 ++++++++++++++++--- 16 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 loco-gen/.gitattributes rename loco-new/base_template/.cargo/{config.toml => config.toml.t} (54%) diff --git a/.github/workflows/loco-new.yml b/.github/workflows/loco-new.yml index 300ab45a2..4db13adee 100644 --- a/.github/workflows/loco-new.yml +++ b/.github/workflows/loco-new.yml @@ -65,6 +65,9 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.6 + - name: Install seaorm cli + run: cargo install sea-orm-cli + - name: Run cargo test run: cargo test --all-features -- --test-threads 1 working-directory: ./loco-new diff --git a/loco-gen/.gitattributes b/loco-gen/.gitattributes new file mode 100644 index 000000000..633873a7e --- /dev/null +++ b/loco-gen/.gitattributes @@ -0,0 +1 @@ +src/templates/* text eol=lf diff --git a/loco-new/base_template/.cargo/config.toml b/loco-new/base_template/.cargo/config.toml.t similarity index 54% rename from loco-new/base_template/.cargo/config.toml rename to loco-new/base_template/.cargo/config.toml.t index fb921ea85..04d71580f 100644 --- a/loco-new/base_template/.cargo/config.toml +++ b/loco-new/base_template/.cargo/config.toml.t @@ -1,4 +1,9 @@ [alias] loco = "run --" +{%- if settings.os == "windows" %} loco-tool = "run --bin tool --" +{% else %} +loco-tool = "run --" +{%- endif %} + playground = "run --example playground" diff --git a/loco-new/base_template/Cargo.toml.t b/loco-new/base_template/Cargo.toml.t index d449a99c9..94358209f 100644 --- a/loco-new/base_template/Cargo.toml.t +++ b/loco-new/base_template/Cargo.toml.t @@ -58,10 +58,12 @@ name = "{{settings.module_name}}-cli" path = "src/bin/main.rs" required-features = [] +{%- if settings.os == "windows" %} [[bin]] name = "tool" path = "src/bin/tool.rs" required-features = [] +{%- endif %} [dev-dependencies] loco-rs = { workspace = true, features = ["testing"] } diff --git a/loco-new/setup.rhai b/loco-new/setup.rhai index dcfffe780..81373261c 100644 --- a/loco-new/setup.rhai +++ b/loco-new/setup.rhai @@ -4,9 +4,11 @@ // Copy core project structure files and directories that are fundamental // to the Rust environment, GitHub actions, and formatting settings. -gen.copy_dirs([".cargo", ".github"]); +gen.copy_dirs([".github"]); gen.copy_files([".gitignore", ".rustfmt.toml", "README.md"]); +gen.copy_template_dir(".cargo"); + // ===================== // Core Source Files // ===================== @@ -20,7 +22,12 @@ gen.copy_template("src/initializers/mod.rs.t"); // initializer module gen.copy_template("src/app.rs.t"); // App root file gen.copy_template("src/lib.rs.t"); // Library entry file gen.copy_template("Cargo.toml.t"); // Project’s cargo configuration -gen.copy_template_dir("src/bin"); // Copies binary directory with templates + +// bin +gen.copy_template("src/bin/main.rs.t"); +if windows { + gen.copy_template("src/bin/tool.rs.t"); +} // ===================== diff --git a/loco-new/src/bin/main.rs b/loco-new/src/bin/main.rs index 1bc78449e..c9329ac65 100644 --- a/loco-new/src/bin/main.rs +++ b/loco-new/src/bin/main.rs @@ -10,7 +10,7 @@ use duct::cmd; use loco::{ generator::{executer, extract_default_template, Generator}, settings::Settings, - wizard, Result, + wizard, Result, OS, }; use tracing::level_filters::LevelFilter; use tracing_subscriber::EnvFilter; @@ -51,12 +51,21 @@ enum Commands { #[arg(long)] assets: Option, - /// Allows create loco starter in target git repository + /// Create the starter in target git repository #[arg(short, long)] allow_in_git_repo: bool, + + /// Create a Unix (linux, mac) or Windows optimized starter + #[arg(long, default_value = DEFAULT_OS)] + os: OS, }, } +#[cfg(unix)] +const DEFAULT_OS: &str = "linux"; +#[cfg(not(unix))] +const DEFAULT_OS: &str = "windows"; + #[allow(clippy::cognitive_complexity)] fn main() -> Result<()> { let cli = Cli::parse(); @@ -76,7 +85,9 @@ fn main() -> Result<()> { assets, name, allow_in_git_repo, + os, } => { + tracing::debug!(path = ?path, db = ?db, bg=?bg, assets=?assets,name=?name, allow_in_git_repo=allow_in_git_repo, os=?os, "CLI options"); if !allow_in_git_repo && is_a_git_repo(path.as_path()).unwrap_or(false) { tracing::debug!("the target directory is a Git repository"); wizard::warn_if_in_git_repo()?; @@ -108,7 +119,7 @@ fn main() -> Result<()> { let executor = executer::FileSystem::new(generator_tmp_folder.as_path(), to.as_path()); - let settings = Settings::from_wizard(&app_name, &user_selection); + let settings = Settings::from_wizard(&app_name, &user_selection, os); if let Ok(path) = env::var("LOCO_DEV_MODE_PATH") { println!("⚠️ NOTICE: working in dev mode, pointing to local Loco on '{path}'"); diff --git a/loco-new/src/generator/mod.rs b/loco-new/src/generator/mod.rs index e6d88085a..a477ab1b6 100644 --- a/loco-new/src/generator/mod.rs +++ b/loco-new/src/generator/mod.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use include_dir::{include_dir, Dir}; use rhai::{Engine, Scope}; -use crate::settings; +use crate::{settings, OS}; static APP_TEMPLATE: Dir<'_> = include_dir!("base_template"); @@ -87,6 +87,7 @@ impl Generator { scope.push("background", self.settings.background.is_some()); scope.push("initializers", self.settings.initializers.is_some()); scope.push("asset", self.settings.asset.is_some()); + scope.push("windows", self.settings.os == OS::Windows); engine.run_with_scope(&mut scope, script)?; Ok(()) diff --git a/loco-new/src/lib.rs b/loco-new/src/lib.rs index 4cf08ce39..dcf03ce12 100644 --- a/loco-new/src/lib.rs +++ b/loco-new/src/lib.rs @@ -1,3 +1,7 @@ +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; +use strum::Display; + pub mod generator; pub mod settings; pub mod wizard; @@ -32,3 +36,17 @@ impl Error { Self::Message(msg.into()) } } + +#[derive(Debug, Clone, Deserialize, Serialize, Display, Default, PartialEq, Eq, ValueEnum)] +pub enum OS { + #[cfg_attr(windows, default)] + #[serde(rename = "windows")] + Windows, + + #[cfg_attr(unix, default)] + #[serde(rename = "linux")] + Linux, + + #[serde(rename = "macos")] + Macos, +} diff --git a/loco-new/src/settings.rs b/loco-new/src/settings.rs index 1e6649162..28df3f5fc 100644 --- a/loco-new/src/settings.rs +++ b/loco-new/src/settings.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ wizard::{self, AssetsOption, BackgroundOption, DBOption}, - LOCO_VERSION, + LOCO_VERSION, OS, }; /// Represents general application settings. @@ -25,6 +25,7 @@ pub struct Settings { pub initializers: Option, pub features: Features, pub loco_version_text: String, + pub os: OS, } impl From for Option { @@ -60,7 +61,7 @@ impl From for Option { impl Settings { /// Creates a new [`Settings`] instance based on prompt selections. #[must_use] - pub fn from_wizard(package_name: &str, prompt_selection: &wizard::Selections) -> Self { + pub fn from_wizard(package_name: &str, prompt_selection: &wizard::Selections, os: OS) -> Self { let features = if prompt_selection.db.enable() { Features::default() } else { @@ -87,6 +88,7 @@ impl Settings { }, features, loco_version_text: get_loco_version_text(), + os, } } } @@ -105,6 +107,7 @@ impl Default for Settings { initializers: Default::default(), features: Default::default(), loco_version_text: get_loco_version_text(), + os: Default::default(), } } } diff --git a/loco-new/tests/templates/db.rs b/loco-new/tests/templates/db.rs index 6fc166407..9be2dfc43 100644 --- a/loco-new/tests/templates/db.rs +++ b/loco-new/tests/templates/db.rs @@ -101,6 +101,7 @@ fn test_src_bin_main_rs( } #[rstest] +#[cfg(windows)] fn test_src_bin_tool_rs( #[values(DBOption::None, DBOption::Sqlite, DBOption::Postgres)] db: DBOption, ) { diff --git a/loco-new/tests/templates/module_name.rs b/loco-new/tests/templates/module_name.rs index 2b015617c..d81a62077 100644 --- a/loco-new/tests/templates/module_name.rs +++ b/loco-new/tests/templates/module_name.rs @@ -36,9 +36,7 @@ fn test_cargo_toml() { } #[rstest] -fn test_use_name( - #[values("src/bin/main.rs", "src/bin/tool.rs", "tests/requests/home.rs")] file: &str, -) { +fn test_use_name(#[values("src/bin/main.rs", "tests/requests/home.rs")] file: &str) { let generator = run_generator(); let content = std::fs::read_to_string(generator.path(file)).expect("could not open file"); diff --git a/loco-new/tests/wizard/new.rs b/loco-new/tests/wizard/new.rs index 7eb6f0b26..26f60a902 100644 --- a/loco-new/tests/wizard/new.rs +++ b/loco-new/tests/wizard/new.rs @@ -3,8 +3,9 @@ use std::{fs, path::PathBuf, sync::Arc}; use duct::cmd; use loco::{ generator::{executer::FileSystem, Generator}, - settings, wizard, - wizard::{AssetsOption, BackgroundOption, DBOption}, + settings, + wizard::{self, AssetsOption, BackgroundOption, DBOption}, + OS, }; use uuid::Uuid; @@ -43,35 +44,48 @@ fn test_all_combinations( #[values(AssetsOption::Serverside, AssetsOption::Clientside, AssetsOption::None)] asset: AssetsOption, ) { - test_combination(db, background, asset); + test_combination(db, background, asset, false); } // when running locally set LOCO_DEV_MODE_PATH= #[test] fn test_starter_combinations() { // lightweight service - test_combination(DBOption::None, BackgroundOption::None, AssetsOption::None); + test_combination( + DBOption::None, + BackgroundOption::None, + AssetsOption::None, + false, + ); // REST API test_combination( DBOption::Sqlite, BackgroundOption::Async, AssetsOption::None, + true, ); // SaaS, serverside test_combination( DBOption::Sqlite, BackgroundOption::Async, AssetsOption::Serverside, + true, ); // SaaS, clientside test_combination( DBOption::Sqlite, BackgroundOption::Async, AssetsOption::Clientside, + true, ); } -fn test_combination(db: DBOption, background: BackgroundOption, asset: AssetsOption) { +fn test_combination( + db: DBOption, + background: BackgroundOption, + asset: AssetsOption, + scaffold: bool, +) { use std::collections::HashMap; let test_dir = TestDir::new(); @@ -83,7 +97,8 @@ fn test_combination(db: DBOption, background: BackgroundOption, asset: AssetsOpt background, asset, }; - let settings = settings::Settings::from_wizard("test-loco-template", &wizard_selection); + let settings = + settings::Settings::from_wizard("test-loco-template", &wizard_selection, OS::default()); let res = Generator::new(Arc::new(executor), settings).run(); assert!(res.is_ok()); @@ -116,4 +131,27 @@ fn test_combination(db: DBOption, background: BackgroundOption, asset: AssetsOpt .dir(test_dir.path.as_path()) .run() .expect("run test"); + + if scaffold { + cmd!( + "cargo", + "loco", + "g", + "scaffold", + "movie", + "title:string", + "--htmx" + ) + .full_env(&env_map) + .dir(test_dir.path.as_path()) + .run() + .expect("scaffold"); + cmd!("cargo", "test") + // .stdout_null() + // .stderr_null() + .full_env(&env_map) + .dir(test_dir.path.as_path()) + .run() + .expect("test after scaffold"); + } } diff --git a/src/controller/middleware/secure_headers.rs b/src/controller/middleware/secure_headers.rs index a899395d2..ff01eaebe 100644 --- a/src/controller/middleware/secure_headers.rs +++ b/src/controller/middleware/secure_headers.rs @@ -227,12 +227,21 @@ where mod tests { use axum::{routing::get, Router}; - use hyper::Method; + use hyper::{HeaderMap, Method}; use insta::assert_debug_snapshot; use tower::ServiceExt; use super::*; - + fn normalize_headers(headers: &HeaderMap) -> BTreeMap { + headers + .iter() + .map(|(k, v)| { + let key = k.to_string(); + let value = v.to_str().unwrap_or("").to_string(); + (key, value) + }) + .collect() + } #[tokio::test] async fn can_set_headers() { let config = SecureHeader { @@ -250,7 +259,7 @@ mod tests { .body(Body::empty()) .unwrap(); let response = app.oneshot(req).await.unwrap(); - assert_debug_snapshot!(response.headers()); + assert_debug_snapshot!(normalize_headers(response.headers())); } #[tokio::test] @@ -274,7 +283,7 @@ mod tests { .body(Body::empty()) .unwrap(); let response = app.oneshot(req).await.unwrap(); - assert_debug_snapshot!(response.headers()); + assert_debug_snapshot!(normalize_headers(response.headers())); } #[tokio::test] @@ -290,6 +299,6 @@ mod tests { .body(Body::empty()) .unwrap(); let response = app.oneshot(req).await.unwrap(); - assert_debug_snapshot!(response.headers()); + assert_debug_snapshot!(normalize_headers(response.headers())); } } diff --git a/src/controller/middleware/snapshots/loco_rs__controller__middleware__secure_headers__tests__can_override_headers.snap b/src/controller/middleware/snapshots/loco_rs__controller__middleware__secure_headers__tests__can_override_headers.snap index 746922468..0243fe4ee 100644 --- a/src/controller/middleware/snapshots/loco_rs__controller__middleware__secure_headers__tests__can_override_headers.snap +++ b/src/controller/middleware/snapshots/loco_rs__controller__middleware__secure_headers__tests__can_override_headers.snap @@ -1,15 +1,16 @@ --- source: src/controller/middleware/secure_headers.rs -expression: res.headers() +expression: normalize_headers(response.headers()) +snapshot_kind: text --- { "content-length": "0", "content-security-policy": "default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'", + "new-header": "baz", "strict-transport-security": "max-age=631138519", "x-content-type-options": "nosniff", "x-download-options": "foobar", "x-frame-options": "sameorigin", "x-permitted-cross-domain-policies": "none", "x-xss-protection": "0", - "new-header": "baz", } diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 33e2b173c..ba170c116 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -42,9 +42,8 @@ impl RunResults { pub fn all_resources(base_dir: &Path) -> Result> { let mut result = vec![]; result.push(run(base_dir).expect("loco lib mast be tested")); - result.extend(run_all_in_folder(&base_dir.join(utils::FOLDER_EXAMPLES))?); - result.extend(run_all_in_folder(&base_dir.join(utils::FOLDER_STARTERS))?); - result.extend(run_all_in_folder(&base_dir.join(utils::FOLDER_LOCO_CLI))?); + result.extend(run_all_in_folder(&base_dir.join("examples"))?); + result.extend(run_all_in_folder(&base_dir.join("loco-new"))?); Ok(result) } @@ -78,7 +77,7 @@ pub fn run(dir: &Path) -> Option { path: dir.to_path_buf(), fmt: cargo_fmt(dir).is_ok(), clippy: cargo_clippy(dir).is_ok(), - test: cargo_test(dir).is_ok(), + test: cargo_test(dir, false).is_ok(), }) } else { None @@ -86,17 +85,23 @@ pub fn run(dir: &Path) -> Option { } /// Run cargo test on the given directory. -fn cargo_test(dir: &Path) -> Result { +pub fn cargo_test(dir: &Path, serial: bool) -> Result { + let mut params = FMT_TEST.to_vec(); + if serial { + params.push("--"); + params.push("--test-threads"); + params.push("1"); + } println!( "Running `cargo {}` in folder {}", - FMT_TEST.join(" "), + params.join(" "), dir.display() ); - Ok(cmd("cargo", FMT_TEST.as_slice()).dir(dir).run()?) + Ok(cmd("cargo", params.as_slice()).dir(dir).run()?) } /// Run cargo fmt on the given directory. -fn cargo_fmt(dir: &Path) -> Result { +pub fn cargo_fmt(dir: &Path) -> Result { println!( "Running `cargo {}` in folder {}", FMT_ARGS.join(" "), @@ -106,7 +111,7 @@ fn cargo_fmt(dir: &Path) -> Result { } /// Run cargo clippy on the given directory. -fn cargo_clippy(dir: &Path) -> Result { +pub fn cargo_clippy(dir: &Path) -> Result { println!( "Running `cargo {}` in folder {}", FMT_CLIPPY.join(" "), diff --git a/xtask/src/versions.rs b/xtask/src/versions.rs index 97762de74..e6e324901 100644 --- a/xtask/src/versions.rs +++ b/xtask/src/versions.rs @@ -1,9 +1,13 @@ -use std::path::Path; +use std::{ + env::{self, current_dir}, + path::Path, +}; +use duct::cmd; use regex::Regex; use crate::{ - ci, + ci::{self, cargo_clippy, cargo_fmt, cargo_test}, errors::{Error, Result}, out, }; @@ -38,10 +42,25 @@ fn bump_version_in_file( } pub fn bump_version(version: &str) -> Result<()> { - // XXX run tests with local loco: - // set LOCO_DEV_MODE_PATH=//projects/loco/ - // and run the loco-new test suite - // + // testing loco-new will test 4 combinations of starters + // sets LOCO_DEV_MODE_PATH=//projects/loco/ and shared cargo build path + let new_path = Path::new("loco-new"); + cargo_fmt(new_path)?; + cargo_clippy(new_path)?; + if env::var("LOCO_DEV_MODE_PATH").is_err() { + let loco_path = current_dir()?.to_string_lossy().to_string(); + println!("setting LOCO_DEV_MODE_PATH to `{loco_path}`"); + env::set_var("LOCO_DEV_MODE_PATH", loco_path); + + // this should accelerate starters compilation + println!("setting CARGO_SHARED_PATH"); + env::set_var("CARGO_SHARED_PATH", "/tmp/cargo-shared-path"); + } + + cmd("cargo", ["test", "--", "--test-threads", "1"].as_slice()) + .dir(new_path) + .run()?; + env::remove_var("CARGO_SHARED_PATH"); // replace main versions let version_replacement = format!(r#"version = "{version}""#); @@ -68,5 +87,25 @@ pub fn bump_version(version: &str) -> Result<()> { true, ); + println!( + " + PUBLISHING + + = framework = + + $ cd loco-gen && cargo publish + $ cargo publish + + = loco 'new' CLI = + + $ cd loco-new && cargo-publish + + = docs = + + $ cd docs-site + $ npm build + $ zola build && netlify deploy -p -d public + " + ); Ok(()) }