diff --git a/.github/skills/dev/git-workflow/run-linters/references/linters.md b/.github/skills/dev/git-workflow/run-linters/references/linters.md index e5446a9c5..971953688 100644 --- a/.github/skills/dev/git-workflow/run-linters/references/linters.md +++ b/.github/skills/dev/git-workflow/run-linters/references/linters.md @@ -235,22 +235,21 @@ cargo fmt ## Linting Framework Architecture -The unified linting framework is implemented in `packages/linting/`: +The unified linting framework is provided by the [`torrust-linting`](https://crates.io/crates/torrust-linting) +external crate (published to crates.io). Source: [github.com/torrust/torrust-linting](https://github.com/torrust/torrust-linting). ```text -packages/linting/ -├── src/ -│ ├── linters/ # Individual linter implementations -│ │ ├── markdown.rs -│ │ ├── yaml.rs -│ │ ├── toml.rs -│ │ ├── cspell.rs -│ │ ├── clippy.rs -│ │ ├── rustfmt.rs -│ │ └── shellcheck.rs -│ ├── runner.rs # Execution logic -│ └── lib.rs -└── README.md +src/ +├── linters/ # Individual linter implementations +│ ├── markdown.rs +│ ├── yaml.rs +│ ├── toml.rs +│ ├── cspell.rs +│ ├── clippy.rs +│ ├── rustfmt.rs +│ └── shellcheck.rs +├── runner.rs # Execution logic +└── lib.rs ``` ### Benefits @@ -314,5 +313,5 @@ cargo run --bin linter --help ## References - [Linting Guide](../../../docs/contributing/linting.md) -- [Linting Framework README](../../../packages/linting/README.md) +- [`torrust-linting` on crates.io](https://crates.io/crates/torrust-linting) - [Pre-Commit Process](../../../docs/contributing/commit-process.md) diff --git a/.github/skills/dev/git-workflow/run-linters/skill.md b/.github/skills/dev/git-workflow/run-linters/skill.md index 9a6340e3a..284b651b0 100644 --- a/.github/skills/dev/git-workflow/run-linters/skill.md +++ b/.github/skills/dev/git-workflow/run-linters/skill.md @@ -185,7 +185,7 @@ For detailed configuration and reference information about each linter, see: - [references/linters.md](references/linters.md) - Comprehensive linter documentation - [docs/contributing/linting.md](../../../docs/contributing/linting.md) - Linting guide -- [packages/linting/README.md](../../../packages/linting/README.md) - Linting framework details +- [`torrust-linting` on crates.io](https://crates.io/crates/torrust-linting) - Linting framework details ## Quick Reference diff --git a/Cargo.toml b/Cargo.toml index e8de3613a..6f91d8ee3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "packages/linting", "packages/dependency-installer", "packages/sdk", "packages/deployer-types", @@ -62,7 +61,7 @@ testcontainers = { version = "0.26", features = [ "blocking" ] } thiserror = "2.0" torrust-dependency-installer = { path = "packages/dependency-installer" } torrust-deployer-types = { path = "packages/deployer-types" } -torrust-linting = { path = "packages/linting" } +torrust-linting = "0.1.0" tracing = { version = "0.1", features = [ "attributes" ] } tracing-appender = "0.2" tracing-subscriber = { version = "0.3", features = [ "env-filter", "json", "fmt" ] } diff --git a/docs/codebase-architecture.md b/docs/codebase-architecture.md index 7dc3f5293..5ca8c226d 100644 --- a/docs/codebase-architecture.md +++ b/docs/codebase-architecture.md @@ -217,7 +217,7 @@ Application initialization and lifecycle management: Independently versioned Cargo workspace packages in `packages/`: -- ✅ `packages/linting/` - Unified linting framework (runs markdownlint, yamllint, taplo, cspell, clippy, rustfmt, shellcheck) +- ✅ [`torrust-linting`](https://crates.io/crates/torrust-linting) (external crate) - Unified linting framework (runs markdownlint, yamllint, taplo, cspell, clippy, rustfmt, shellcheck) - ✅ `packages/dependency-installer/` - Dependency detection and installation for development setup (OpenTofu, Ansible, LXD, cargo-machete) - ✅ `packages/deployer-types/` - Shared value objects and traits (`torrust-deployer-types`) — cross-cutting foundational types (e.g., `EnvironmentName`, `DomainName`, `Username`, `Clock`, `ErrorKind`) shared by the root crate and SDK - ✅ `packages/sdk/` - Programmatic SDK (`torrust-tracker-deployer-sdk`) — independently consumable Rust crate for deploying Torrust Tracker instances without the CLI diff --git a/docs/contributing/linting.md b/docs/contributing/linting.md index 83bf199f2..f5737c07f 100644 --- a/docs/contributing/linting.md +++ b/docs/contributing/linting.md @@ -76,7 +76,7 @@ All linting is managed through a unified Rust binary (`src/bin/linter.rs`) that - **Unified logging**: Consistent output formatting - **Easy extensibility**: Add new linters by implementing the `Linter` trait -The linter binary is part of the `torrust-linting` package (`packages/linting/`), which provides a reusable linting framework. +The linter binary uses the [`torrust-linting`](https://crates.io/crates/torrust-linting) crate, which provides a reusable linting framework published to crates.io. ### Alternative: Shell Script Wrapper diff --git a/docs/contributing/testing/quality/coverage.md b/docs/contributing/testing/quality/coverage.md index de2748db9..c32508c8a 100644 --- a/docs/contributing/testing/quality/coverage.md +++ b/docs/contributing/testing/quality/coverage.md @@ -53,7 +53,7 @@ When mocking adds no value or requires real infrastructure: #### 4. Linting Package -- **Location**: `packages/linting/` +- **Location**: [`torrust-linting`](https://crates.io/crates/torrust-linting) (external crate) - **Reason**: Primarily executed as binary, wraps external tools - **Coverage**: 30-40% is acceptable - **Testing**: Validated through actual execution @@ -179,7 +179,7 @@ All coverage commands use cargo aliases defined in `.cargo/config.toml`: | Alias | Full Command | Purpose | | ------------------- | ----------------------------------------------------------------- | --------------------------------- | | `cargo cov` | `cargo llvm-cov` | Basic coverage report in terminal | -| `cargo cov-check` | `cargo llvm-cov --all-features --workspace --fail-under-lines 70` | Validate coverage threshold | +| `cargo cov-check` | `cargo llvm-cov --all-features --workspace --fail-under-lines 70` | Validate coverage threshold | | `cargo cov-lcov` | `cargo llvm-cov --lcov --output-path=./.coverage/lcov.info` | Generate LCOV format | | `cargo cov-codecov` | `cargo llvm-cov --codecov --output-path=./.coverage/codecov.json` | Generate Codecov JSON | | `cargo cov-html` | `cargo llvm-cov --html` | Generate HTML report | diff --git a/packages/README.md b/packages/README.md index d6df7125b..9f91a498a 100644 --- a/packages/README.md +++ b/packages/README.md @@ -26,26 +26,6 @@ This directory contains reusable Rust workspace packages that support the Torrus **Documentation**: See [packages/dependency-installer/README.md](./dependency-installer/README.md) -### [`linting/`](./linting/) - -**Purpose**: Unified linting framework for Rust projects - -**Key Features**: - -- Supports multiple linters: markdown, YAML, TOML, Rust (clippy + rustfmt), shellcheck -- Pre-built CLI components for easy binary creation -- Extensible architecture for adding new linters -- Uses existing configuration files (`.taplo.toml`, `.yamllint.yml`, etc.) - -**Use Cases**: - -- Enforcing code quality standards -- Pre-commit validation -- CI/CD linting pipelines -- Standardizing linting across multiple projects - -**Documentation**: See [packages/linting/README.md](./linting/README.md) - ### [`deployer-types/`](./deployer-types/) **Purpose**: Shared value objects and traits for the Torrust Tracker Deployer ecosystem @@ -108,7 +88,7 @@ All packages in this directory: ```rust // Add to your Cargo.toml [dependencies] -torrust-linting = { path = "packages/linting" } +torrust-linting = "0.1.0" # external crate: https://crates.io/crates/torrust-linting torrust-dependency-installer = { path = "packages/dependency-installer" } torrust-tracker-deployer-sdk = { path = "packages/sdk" } ``` diff --git a/packages/linting/Cargo.toml b/packages/linting/Cargo.toml deleted file mode 100644 index 74a202cc2..000000000 --- a/packages/linting/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "torrust-linting" -version = "0.1.0" -edition = "2021" -description = "Linting utilities for the Torrust Tracker Deployer project" -license = "MIT" - -[lib] -name = "torrust_linting" -path = "src/lib.rs" - -[dependencies] -anyhow = "1.0" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = [ "env-filter" ] } -clap = { version = "4.0", features = [ "derive" ] } diff --git a/packages/linting/README.md b/packages/linting/README.md deleted file mode 100644 index cabe4f729..000000000 --- a/packages/linting/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# Torrust Linting Package - -This package provides a unified linting framework that can be easily integrated into any Rust project. - -## Features - -- **Multiple Linters**: Supports markdown, YAML, TOML, Rust (clippy + rustfmt), and shell script linting -- **CLI Ready**: Pre-built CLI components for easy binary creation -- **Extensible**: Easy to add new linters -- **Configurable**: Uses existing configuration files (.taplo.toml, .yamllint.yml, etc.) - -## Usage - -### Option 1: Use the Complete CLI (Easiest) - -Create a simple binary that uses the complete CLI: - -```rust -// src/bin/linter.rs -use anyhow::Result; - -fn main() -> Result<()> { - torrust_linting::run_cli() -} -``` - -This gives you a full-featured linter CLI with all commands and help text. - -### Option 2: Custom CLI Implementation - -For more control, you can use the individual components: - -```rust -use anyhow::Result; -use clap::Parser; -use torrust_linting::{Cli, execute_command, init_tracing}; - -fn main() -> Result<()> { - init_tracing(); - - let cli = Cli::parse(); - execute_command(cli.command)?; - - Ok(()) -} -``` - -### Option 3: Use Individual Linters - -Use specific linters programmatically: - -```rust -use anyhow::Result; -use torrust_linting::{run_rustfmt_linter, run_clippy_linter, run_all_linters}; - -fn main() -> Result<()> { - // Run individual linters - run_rustfmt_linter()?; - run_clippy_linter()?; - - // Or run all linters at once - run_all_linters()?; - - Ok(()) -} -``` - -### Option 4: Custom Command Structure - -Build your own CLI with custom commands: - -```rust -use anyhow::Result; -use clap::{Parser, Subcommand}; -use torrust_linting::{run_rustfmt_linter, run_clippy_linter}; - -#[derive(Parser)] -struct MyCli { - #[command(subcommand)] - command: MyCommands, -} - -#[derive(Subcommand)] -enum MyCommands { - /// Check Rust formatting - Format, - /// Run Rust linting - Lint, -} - -fn main() -> Result<()> { - let cli = MyCli::parse(); - - match cli.command { - MyCommands::Format => run_rustfmt_linter()?, - MyCommands::Lint => run_clippy_linter()?, - } - - Ok(()) -} -``` - -## Adding to Your Project - -Add to your `Cargo.toml`: - -```toml -[dependencies] -torrust-linting = { path = "path/to/torrust-linting" } -``` - -Or if using in a workspace: - -```toml -[workspace] -members = ["packages/torrust-linting"] - -[dependencies] -torrust-linting = { path = "packages/torrust-linting" } -``` - -## Available Linters - -- **Markdown**: Uses markdownlint-cli -- **YAML**: Uses yamllint -- **TOML**: Uses taplo -- **Rust Clippy**: Uses cargo clippy -- **Rust Format**: Uses cargo fmt -- **Shell**: Uses shellcheck - -## Configuration - -The linting package respects existing configuration files: - -- `.taplo.toml` for TOML formatting -- `.yamllint.yml` for YAML linting -- `.markdownlint.json` for Markdown linting -- Standard Rust tooling configuration for clippy and rustfmt diff --git a/packages/linting/examples/custom_cli.rs b/packages/linting/examples/custom_cli.rs deleted file mode 100644 index 6d51912ca..000000000 --- a/packages/linting/examples/custom_cli.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Example: Custom CLI with individual linters -//! -//! This example shows how to create a custom CLI that uses individual -//! linter functions from the torrust-linting package. - -use anyhow::Result; -use clap::{Parser, Subcommand}; -use torrust_linting::{ - run_clippy_linter, run_markdown_linter, run_rustfmt_linter, run_shellcheck_linter, - run_toml_linter, run_yaml_linter, -}; - -#[derive(Parser)] -#[command(name = "custom-linter")] -#[command(about = "A custom linter CLI using torrust-linting")] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Lint only Rust code (clippy + rustfmt) - Rust, - /// Lint only markup files (markdown + yaml + toml) - Markup, - /// Lint only shell scripts - Shell, -} - -fn main() -> Result<()> { - // Initialize logging (you can customize this) - torrust_linting::init_tracing(); - - let cli = Cli::parse(); - - match cli.command { - Commands::Rust => { - println!("🦀 Running Rust linters..."); - run_clippy_linter()?; - run_rustfmt_linter()?; - } - Commands::Markup => { - println!("📄 Running markup linters..."); - run_markdown_linter()?; - run_yaml_linter()?; - run_toml_linter()?; - } - Commands::Shell => { - println!("🐚 Running shell script linter..."); - run_shellcheck_linter()?; - } - } - - println!("✅ All selected linters passed!"); - Ok(()) -} diff --git a/packages/linting/examples/simple_linter.rs b/packages/linting/examples/simple_linter.rs deleted file mode 100644 index a32eaf938..000000000 --- a/packages/linting/examples/simple_linter.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Example: Simple linter binary -//! -//! This example shows how to create the simplest possible linter binary -//! using the torrust-linting package. - -use anyhow::Result; - -fn main() -> Result<()> { - // This single line gives you a complete linter CLI! - torrust_linting::run_cli() -} diff --git a/packages/linting/src/cli.rs b/packages/linting/src/cli.rs deleted file mode 100644 index 6d21131fa..000000000 --- a/packages/linting/src/cli.rs +++ /dev/null @@ -1,211 +0,0 @@ -use anyhow::Result; -use clap::{CommandFactory, Parser, Subcommand}; -use tracing::{error, info, Level}; - -use crate::linters::{ - run_clippy_linter, run_cspell_linter, run_markdown_linter, run_rustfmt_linter, - run_shellcheck_linter, run_toml_linter, run_yaml_linter, -}; - -/// Initialize tracing with default configuration -pub fn init_tracing() { - tracing_subscriber::fmt() - .with_target(true) - .with_thread_ids(false) - .with_thread_names(false) - .with_level(true) - .with_max_level(Level::INFO) - .init(); -} - -/// Run the complete linter CLI application -/// -/// This function initializes tracing, parses CLI arguments, and executes the appropriate linting commands. -/// -/// # Errors -/// -/// Returns an error if any linter fails or if CLI parsing fails. -pub fn run_cli() -> Result<()> { - init_tracing(); - - let cli = Cli::parse(); - execute_command(cli.command.as_ref())?; - - Ok(()) -} -#[derive(Parser)] -#[command(name = "linter")] -#[command(about = "Unified linting tool")] -#[command(version)] -pub struct Cli { - #[command(subcommand)] - pub command: Option, -} - -#[derive(Subcommand)] -pub enum Commands { - /// Run markdown linter - #[command(alias = "md")] - Markdown, - - /// Run YAML linter - Yaml, - - /// Run TOML linter using Taplo - Toml, - - /// Run `CSpell` spell checker - #[command(alias = "spell")] - Cspell, - - /// Run Rust clippy linter - Clippy, - - /// Run Rust formatter check - #[command(alias = "fmt")] - Rustfmt, - - /// Run `ShellCheck` linter - #[command(alias = "shell")] - Shellcheck, - - /// Run all linters - All, -} - -/// Run all linters and collect results -/// -/// # Errors -/// -/// Returns an error if any linter fails. -pub fn run_all_linters() -> Result<()> { - info!("Running All Linters"); - - let mut failed = false; - - // Run markdown linter - match run_markdown_linter() { - Ok(()) => {} - Err(e) => { - error!("Markdown linting failed: {e}"); - failed = true; - } - } - - // Run YAML linter - match run_yaml_linter() { - Ok(()) => {} - Err(e) => { - error!("YAML linting failed: {e}"); - failed = true; - } - } - - // Run TOML linter - match run_toml_linter() { - Ok(()) => {} - Err(e) => { - error!("TOML linting failed: {e}"); - failed = true; - } - } - - // Run CSpell spell checker - match run_cspell_linter() { - Ok(()) => {} - Err(e) => { - error!("Spell checking failed: {e}"); - failed = true; - } - } - - // Run Rust clippy linter - match run_clippy_linter() { - Ok(()) => {} - Err(e) => { - error!("Rust clippy linting failed: {e}"); - failed = true; - } - } - - // Run Rust formatter check - match run_rustfmt_linter() { - Ok(()) => {} - Err(e) => { - error!("Rust formatting failed: {e}"); - failed = true; - } - } - - // Run ShellCheck linter - match run_shellcheck_linter() { - Ok(()) => {} - Err(e) => { - error!("Shell script linting failed: {e}"); - failed = true; - } - } - - if failed { - error!("Some linters failed"); - return Err(anyhow::anyhow!("Some linters failed")); - } - info!("All linters passed"); - Ok(()) -} - -/// Execute the linting command -/// -/// # Errors -/// -/// Returns an error if any linter fails. -pub fn execute_command(command: Option<&Commands>) -> Result<()> { - match command { - Some(Commands::Markdown) => { - run_markdown_linter()?; - } - Some(Commands::Yaml) => { - run_yaml_linter()?; - } - Some(Commands::Toml) => { - run_toml_linter()?; - } - Some(Commands::Cspell) => { - run_cspell_linter()?; - } - Some(Commands::Clippy) => { - run_clippy_linter()?; - } - Some(Commands::Rustfmt) => { - run_rustfmt_linter()?; - } - Some(Commands::Shellcheck) => { - run_shellcheck_linter()?; - } - Some(Commands::All) => { - run_all_linters()?; - } - None => { - // Show help when no command is provided - let mut cmd = Cli::command(); - cmd.print_help()?; - print_usage_examples(); - } - } - - Ok(()) -} - -/// Print usage examples -pub fn print_usage_examples() { - println!("\n"); - println!("Examples:"); - println!(" cargo run --bin linter markdown # Run markdown linter"); - println!(" cargo run --bin linter yaml # Run YAML linter"); - println!(" cargo run --bin linter toml # Run TOML linter"); - println!(" cargo run --bin linter cspell # Run CSpell spell checker"); - println!(" cargo run --bin linter clippy # Run Rust clippy linter"); - println!(" cargo run --bin linter rustfmt # Run Rust formatter check"); - println!(" cargo run --bin linter shellcheck # Run ShellCheck linter"); - println!(" cargo run --bin linter all # Run all linters"); -} diff --git a/packages/linting/src/lib.rs b/packages/linting/src/lib.rs deleted file mode 100644 index 0ad1e3908..000000000 --- a/packages/linting/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod cli; -pub mod linters; -pub mod utils; - -pub use cli::*; -pub use linters::*; -pub use utils::*; diff --git a/packages/linting/src/linters/clippy.rs b/packages/linting/src/linters/clippy.rs deleted file mode 100644 index 2c1d8ca92..000000000 --- a/packages/linting/src/linters/clippy.rs +++ /dev/null @@ -1,62 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info}; - -/// Run the Rust clippy linter -/// -/// # Errors -/// -/// Returns an error if cargo clippy is not available or if the linting fails. -pub fn run_clippy_linter() -> Result<()> { - let t = Instant::now(); - info!(target: "clippy", "Running Rust Clippy linter..."); - - let mut cmd = Command::new("cargo"); - cmd.env("CARGO_INCREMENTAL", "0").args([ - "clippy", - "--quiet", - "--no-deps", - "--tests", - "--benches", - "--examples", - "--workspace", - "--all-targets", - "--all-features", - "--", - "-D", - "clippy::correctness", - "-D", - "clippy::suspicious", - "-D", - "clippy::complexity", - "-D", - "clippy::perf", - "-D", - "clippy::style", - "-D", - "clippy::pedantic", - ]); - - let output = cmd.output()?; - - if output.status.success() { - info!(target: "clippy", "Clippy linting completed successfully! ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // Print the output from clippy - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "clippy", "Clippy linting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64()); - Err(anyhow::anyhow!("Clippy linting failed")) - } -} diff --git a/packages/linting/src/linters/cspell.rs b/packages/linting/src/linters/cspell.rs deleted file mode 100644 index 5cad86f43..000000000 --- a/packages/linting/src/linters/cspell.rs +++ /dev/null @@ -1,56 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info}; - -use crate::utils::{install_npm_tool, is_command_available}; - -/// Run the `CSpell` spell checker linter -/// -/// # Errors -/// -/// Returns an error if `CSpell` is not available, cannot be installed, -/// or if the spell checking fails. -pub fn run_cspell_linter() -> Result<()> { - // Check if cspell is installed - if !is_command_available("cspell") { - install_npm_tool("cspell")?; - } - - // Run the spell checker - let t = Instant::now(); - info!(target: "cspell", "Running spell check on all files..."); - - // Run cspell on the entire project (it will use cspell.json configuration) - // Note: We explicitly include .github/** because cspell skips dot directories by default - let mut cmd = Command::new("cspell"); - cmd.args([".", ".github/**/*", "--no-progress", "--show-context"]); - - let output = cmd.output()?; - - if output.status.success() { - info!(target: "cspell", "All files passed spell checking! ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // Print the output from cspell (it usually goes to stdout) - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - println!("💡 To fix spelling issues:"); - println!(" 1. Fix actual misspellings in the files"); - println!(" 2. Add technical terms/proper nouns to project-words.txt"); - println!(" 3. Use cspell suggestions: cspell --show-suggestions "); - println!(); - - error!(target: "cspell", "Spell checking failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64()); - Err(anyhow::anyhow!("Spell checking failed")) - } -} diff --git a/packages/linting/src/linters/markdown.rs b/packages/linting/src/linters/markdown.rs deleted file mode 100644 index a1e4f1bcd..000000000 --- a/packages/linting/src/linters/markdown.rs +++ /dev/null @@ -1,83 +0,0 @@ -use anyhow::Result; -use std::env; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info}; - -use crate::utils::{install_npm_tool, is_command_available}; - -/// Run the markdown linter -/// -/// # Errors -/// -/// Returns an error if markdownlint is not available, cannot be installed, -/// or if the linting fails. -pub fn run_markdown_linter() -> Result<()> { - // Check if markdownlint is installed - if !is_command_available("markdownlint") { - install_npm_tool("markdownlint-cli")?; - } - - // Get the current working directory (should be repo root) - let repo_root = env::current_dir()?; - let config_path = repo_root.join(".markdownlint.json"); - - // Run the linter - let t = Instant::now(); - info!(target: "markdown", "Scanning markdown files..."); - - // Find all markdown files, excluding terraform and target directories - let find_output = Command::new("find") - .current_dir(&repo_root) - .args([ - ".", - "-name", - "*.md", - "-type", - "f", - "-not", - "-path", - "*/.terraform/*", - "-not", - "-path", - "./target/*", - ]) - .output()?; - - if !find_output.status.success() { - error!(target: "markdown", "Failed to find markdown files"); - return Err(anyhow::anyhow!("Failed to find markdown files")); - } - - let files = String::from_utf8_lossy(&find_output.stdout); - let file_list: Vec<&str> = files.lines().filter(|s| !s.is_empty()).collect(); - - // Run markdownlint on all found files - let mut cmd = Command::new("markdownlint"); - cmd.current_dir(&repo_root); - cmd.arg("--config").arg(&config_path); - cmd.arg("--dot"); // Include files in dot directories like .github/ - cmd.args(&file_list); - - let output = cmd.output()?; - - if output.status.success() { - info!(target: "markdown", "All markdown files passed linting! ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // Print the output from markdownlint (it usually goes to stdout) - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "markdown", "Markdown linting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64()); - Err(anyhow::anyhow!("Markdown linting failed")) - } -} diff --git a/packages/linting/src/linters/mod.rs b/packages/linting/src/linters/mod.rs deleted file mode 100644 index defe620c2..000000000 --- a/packages/linting/src/linters/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub mod clippy; -pub mod cspell; -pub mod markdown; -pub mod rustfmt; -pub mod shellcheck; -pub mod toml; -pub mod yaml; - -pub use clippy::*; -pub use cspell::*; -pub use markdown::*; -pub use rustfmt::*; -pub use shellcheck::*; -pub use toml::*; -pub use yaml::*; diff --git a/packages/linting/src/linters/rustfmt.rs b/packages/linting/src/linters/rustfmt.rs deleted file mode 100644 index 40676e82a..000000000 --- a/packages/linting/src/linters/rustfmt.rs +++ /dev/null @@ -1,38 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info}; - -/// Run the Rust formatter check -/// -/// # Errors -/// -/// Returns an error if cargo fmt is not available or if the formatting check fails. -pub fn run_rustfmt_linter() -> Result<()> { - let t = Instant::now(); - info!(target: "rustfmt", "Running Rust formatter check..."); - - let output = Command::new("cargo") - .args(["fmt", "--check", "--quiet"]) - .output()?; - - if output.status.success() { - info!(target: "rustfmt", "Rust formatting check passed! ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // Print the output from cargo fmt - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "rustfmt", "Rust formatting check failed. Run 'cargo fmt' to fix formatting. ({:.3}s)", t.elapsed().as_secs_f64()); - Err(anyhow::anyhow!("Rust formatting check failed")) - } -} diff --git a/packages/linting/src/linters/shellcheck.rs b/packages/linting/src/linters/shellcheck.rs deleted file mode 100644 index 39c37b8dc..000000000 --- a/packages/linting/src/linters/shellcheck.rs +++ /dev/null @@ -1,173 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info, warn}; - -use crate::utils::is_command_available; - -/// Install shellcheck using system package manager -/// -/// # Errors -/// -/// Returns an error if no supported package manager is found or if installation fails. -fn install_shellcheck() -> Result<()> { - info!("Installing ShellCheck..."); - - // Try different package managers - if is_command_available("apt-get") { - let output = Command::new("sudo").args(["apt-get", "update"]).output()?; - - if !output.status.success() { - warn!("Failed to update package list"); - } - - let output = Command::new("sudo") - .args(["apt-get", "install", "-y", "shellcheck"]) - .output()?; - - if output.status.success() { - info!("shellcheck installed successfully"); - return Ok(()); - } - } else if is_command_available("dnf") { - let output = Command::new("sudo") - .args(["dnf", "install", "-y", "ShellCheck"]) - .output()?; - - if output.status.success() { - info!("shellcheck installed successfully"); - return Ok(()); - } - } else if is_command_available("pacman") { - let output = Command::new("sudo") - .args(["pacman", "-S", "--noconfirm", "shellcheck"]) - .output()?; - - if output.status.success() { - info!("shellcheck installed successfully"); - return Ok(()); - } - } else if is_command_available("brew") { - let output = Command::new("brew") - .args(["install", "shellcheck"]) - .output()?; - - if output.status.success() { - info!("shellcheck installed successfully"); - return Ok(()); - } - } - - error!("Could not install shellcheck: unsupported package manager"); - info!("Please install shellcheck manually: https://github.com/koalaman/shellcheck#installing"); - Err(anyhow::anyhow!("Could not install shellcheck")) -} - -/// Find shell scripts in the current directory -/// -/// # Errors -/// -/// Returns an error if the find command fails. -fn find_shell_scripts() -> Result> { - let mut files = Vec::new(); - - // Find .sh files - let sh_output = Command::new("find") - .args([ - ".", - "-name", - "*.sh", - "-type", - "f", - "-not", - "-path", - "*/.git/*", - "-not", - "-path", - "*/.terraform/*", - ]) - .output()?; - - if sh_output.status.success() { - let stdout = String::from_utf8_lossy(&sh_output.stdout); - files.extend(stdout.lines().filter(|s| !s.is_empty()).map(String::from)); - } - - // Find .bash files - let bash_output = Command::new("find") - .args([ - ".", - "-name", - "*.bash", - "-type", - "f", - "-not", - "-path", - "*/.git/*", - "-not", - "-path", - "*/.terraform/*", - ]) - .output()?; - - if bash_output.status.success() { - let stdout = String::from_utf8_lossy(&bash_output.stdout); - files.extend(stdout.lines().filter(|s| !s.is_empty()).map(String::from)); - } - - Ok(files) -} - -/// Run the `ShellCheck` linter -/// -/// # Errors -/// -/// Returns an error if shellcheck is not available, cannot be installed, -/// or if the linting fails. -pub fn run_shellcheck_linter() -> Result<()> { - let t = Instant::now(); - info!(target: "shellcheck", "Running ShellCheck on shell scripts..."); - - // Check if shellcheck is installed - if !is_command_available("shellcheck") { - warn!(target: "shellcheck", "shellcheck not found. Attempting to install..."); - install_shellcheck()?; - } - - // Find shell scripts - let shell_files = find_shell_scripts()?; - - if shell_files.is_empty() { - warn!(target: "shellcheck", "No shell scripts found ({:.3}s)", t.elapsed().as_secs_f64()); - return Ok(()); - } - - info!(target: "shellcheck", "Found {} shell script(s) to check", shell_files.len()); - - // Prepare the shellcheck command - let mut cmd = Command::new("shellcheck"); - cmd.args(["--source-path=SCRIPTDIR", "--exclude=SC1091"]); - cmd.args(&shell_files); - - let output = cmd.output()?; - - if output.status.success() { - info!(target: "shellcheck", "shellcheck passed ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // Print the output from shellcheck - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "shellcheck", "shellcheck failed ({:.3}s)", t.elapsed().as_secs_f64()); - Err(anyhow::anyhow!("shellcheck failed")) - } -} diff --git a/packages/linting/src/linters/toml.rs b/packages/linting/src/linters/toml.rs deleted file mode 100644 index c7bd0c561..000000000 --- a/packages/linting/src/linters/toml.rs +++ /dev/null @@ -1,98 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info}; - -use crate::utils::is_command_available; - -/// Install Taplo CLI using cargo -/// -/// # Errors -/// -/// Returns an error if cargo is not available or if the installation fails. -fn install_taplo() -> Result<()> { - info!("Installing Taplo CLI..."); - - // Check if cargo is available - if !is_command_available("cargo") { - error!("Cargo is required to install Taplo CLI"); - return Err(anyhow::anyhow!("Cargo is not available")); - } - - let output = Command::new("cargo") - .args(["install", "taplo-cli", "--locked"]) - .output()?; - - if output.status.success() { - info!("Taplo CLI installed successfully"); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - error!("Failed to install Taplo CLI: {}", stderr); - Err(anyhow::anyhow!("Failed to install Taplo CLI")) - } -} - -/// Run the TOML linter using Taplo -/// -/// # Errors -/// -/// Returns an error if Taplo is not available, cannot be installed, -/// or if the linting fails. -pub fn run_toml_linter() -> Result<()> { - // Check if taplo is installed - if !is_command_available("taplo") { - install_taplo()?; - } - - let t = Instant::now(); - info!(target: "toml", "Scanning TOML files..."); - - // Run taplo check with recursive glob pattern - let check_output = Command::new("taplo") - .args(["check", "**/*.toml"]) - .output()?; - - if !check_output.status.success() { - let stderr = String::from_utf8_lossy(&check_output.stderr); - let stdout = String::from_utf8_lossy(&check_output.stdout); - - // Print the output from taplo - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "toml", "TOML linting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64()); - return Err(anyhow::anyhow!("TOML linting failed")); - } - - // Run taplo format check with recursive glob pattern - let format_output = Command::new("taplo") - .args(["fmt", "--check", "**/*.toml"]) - .output()?; - - if format_output.status.success() { - info!(target: "toml", "All TOML files passed linting and formatting checks! ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&format_output.stderr); - let stdout = String::from_utf8_lossy(&format_output.stdout); - - // Print the output from taplo - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "toml", "TOML formatting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64()); - error!(target: "toml", "Run 'taplo fmt **/*.toml' to auto-fix formatting issues."); - Err(anyhow::anyhow!("TOML formatting failed")) - } -} diff --git a/packages/linting/src/linters/yaml.rs b/packages/linting/src/linters/yaml.rs deleted file mode 100644 index cd438283f..000000000 --- a/packages/linting/src/linters/yaml.rs +++ /dev/null @@ -1,104 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use std::time::Instant; -use tracing::{error, info, warn}; - -use crate::utils::is_command_available; - -/// Install yamllint using system package manager -/// -/// # Errors -/// -/// Returns an error if no supported package manager is found or if installation fails. -fn install_yamllint() -> Result<()> { - info!("Installing yamllint..."); - - // Try different package managers - if is_command_available("apt-get") { - let output = Command::new("sudo").args(["apt-get", "update"]).output()?; - - if !output.status.success() { - warn!("Failed to update package list"); - } - - let output = Command::new("sudo") - .args(["apt-get", "install", "-y", "yamllint"]) - .output()?; - - if output.status.success() { - info!("yamllint installed successfully"); - return Ok(()); - } - } else if is_command_available("dnf") { - let output = Command::new("sudo") - .args(["dnf", "install", "-y", "yamllint"]) - .output()?; - - if output.status.success() { - info!("yamllint installed successfully"); - return Ok(()); - } - } else if is_command_available("pacman") { - let output = Command::new("sudo") - .args(["pacman", "-S", "--noconfirm", "yamllint"]) - .output()?; - - if output.status.success() { - info!("yamllint installed successfully"); - return Ok(()); - } - } else if is_command_available("pip3") { - let output = Command::new("pip3") - .args(["install", "--user", "yamllint"]) - .output()?; - - if output.status.success() { - info!("yamllint installed successfully"); - return Ok(()); - } - } - - error!("Could not install yamllint. Please install it manually."); - Err(anyhow::anyhow!("Could not install yamllint")) -} - -/// Run the YAML linter -/// -/// # Errors -/// -/// Returns an error if yamllint is not available, cannot be installed, -/// or if the linting fails. -pub fn run_yaml_linter() -> Result<()> { - // Check if yamllint is installed - if !is_command_available("yamllint") { - install_yamllint()?; - } - - // Run the linter with the configuration file - let t = Instant::now(); - info!(target: "yaml", "Scanning YAML files..."); - - let output = Command::new("yamllint") - .args(["-c", ".yamllint-ci.yml", "."]) - .output()?; - - if output.status.success() { - info!(target: "yaml", "All YAML files passed linting! ({:.3}s)", t.elapsed().as_secs_f64()); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // Print the output from yamllint - if !stdout.is_empty() { - println!("{stdout}"); - } - if !stderr.is_empty() { - eprintln!("{stderr}"); - } - - println!(); - error!(target: "yaml", "YAML linting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64()); - Err(anyhow::anyhow!("YAML linting failed")) - } -} diff --git a/packages/linting/src/utils.rs b/packages/linting/src/utils.rs deleted file mode 100644 index eb42a32b3..000000000 --- a/packages/linting/src/utils.rs +++ /dev/null @@ -1,36 +0,0 @@ -use anyhow::Result; -use std::process::Command; -use tracing::{error, info}; - -/// Check if a command is available in the system PATH -/// -/// # Errors -/// -/// Returns false if the command is not found or if the check fails. -#[must_use] -pub fn is_command_available(command: &str) -> bool { - Command::new("which") - .arg(command) - .output() - .is_ok_and(|output| output.status.success()) -} - -/// Install a tool using npm globally -/// -/// # Errors -/// -/// Returns an error if npm is not available or if the installation fails. -pub fn install_npm_tool(tool: &str) -> Result<()> { - info!("Installing {tool}..."); - - let output = Command::new("npm").args(["install", "-g", tool]).output()?; - - if output.status.success() { - info!("{tool} installed successfully"); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - error!("Failed to install {tool}: {stderr}"); - Err(anyhow::anyhow!("Failed to install {tool}")) - } -}