# ╔══════════════════════════════════════════════════════════════════════╗ # ║ CI/CD RECIPES ║ # ║ Validation and deployment tasks ║ # ╚══════════════════════════════════════════════════════════════════════╝ # Workspace root directory WORKSPACE_ROOT := justfile_directory() # Help for CI module help: @echo "CI/CD MODULE" @echo "" @echo "Validation:" @echo " just ci::lint Lint all code" @echo " just ci::test-all Test all features" @echo " just ci::check Check + format + lint" @echo " just ci::audit Audit dependencies (security)" @echo "" @echo "Compliance:" @echo " just ci::verify-sbom Verify SBOMs are up to date" @echo " just ci::audit Audit dependencies (security)" @echo " just ci::deny Check dependencies with cargo-deny" @echo "" @echo "Build validation:" @echo " just ci::build-debug Debug build" @echo " just ci::build-release Release build" @echo "" @echo "Full pipeline:" @echo " just ci::full Complete CI: check + test + build" # === VALIDATION STAGE === # Format check [doc("Check format")] fmt-check: @echo "=== Format validation ===" cargo fmt -p typedialog-core -p typedialog -p typedialog-tui -p typedialog-web -p typedialog-ai -p typedialog-prov-gen -p typedialog-ag-core -p typedialog-ag -- --check @echo "✓ Format valid" # Lint [doc("Run clippy")] lint: #!/bin/bash set -e echo "=== Linting ===" if [ -d "target/debug" ]; then echo " → Using: target/debug (exists)" cargo clippy --all-targets --all-features -- -D warnings else echo " → Using: /tmp/typedialog-ci (target/debug does not exist)" cargo clippy --target-dir=/tmp/typedialog-ci --all-targets --all-features -- -D warnings fi echo "✓ Lint passed" # === MULTI-LANGUAGE LINTING === # Lint Rust [doc("Lint Rust (CI)")] lint-rust: just dev::lint-rust # Lint bash scripts [doc("Lint bash (CI)")] lint-bash: just dev::lint-bash # Lint Nickel files [doc("Lint Nickel (CI)")] lint-nickel: just dev::lint-nickel # Lint Nushell scripts [doc("Lint Nushell (CI)")] lint-nushell: just dev::lint-nushell # Lint Markdown [doc("Lint Markdown (CI)")] lint-markdown: just dev::lint-markdown # Lint all languages (CI) [doc("Lint all languages (CI)")] lint-all: just dev::lint-all # Check formatting and linting (all languages) [doc("Check: format + lint (all)")] check: just ci::fmt-check just ci::lint-all @echo "✓ All checks passed" # === TEST STAGE === # Test all features [doc("Test all features")] test-all: #!/bin/bash set -e cd "{{ WORKSPACE_ROOT }}" echo "=== Testing (all features) ===" if [ -d "target/debug" ]; then echo " → Using: target/debug (exists)" cargo test --workspace --all-features else echo " → Using: /tmp/typedialog-ci (target/debug does not exist)" cargo test --target-dir=/tmp/typedialog-ci --workspace --all-features fi echo "✓ All tests passed" # Test default features [doc("Test default features")] test-default: #!/bin/bash set -e cd "{{ WORKSPACE_ROOT }}" echo "=== Testing (default features) ===" if [ -d "target/debug" ]; then echo " → Using: target/debug (exists)" cargo test --workspace else echo " → Using: /tmp/typedialog-ci (target/debug does not exist)" cargo test --target-dir=/tmp/typedialog-ci --workspace fi echo "✓ Tests passed (default features)" # Test integration only [doc("Test integration")] test-integration: #!/bin/bash set -e cd "{{ WORKSPACE_ROOT }}" echo "=== Testing integration ===" if [ -d "target/debug" ]; then echo " → Using: target/debug (exists)" cargo test --test '*' --all-features else echo " → Using: /tmp/typedialog-ci (target/debug does not exist)" cargo test --target-dir=/tmp/typedialog-ci --test '*' --all-features fi echo "✓ Integration tests passed" # === BUILD STAGE === # Debug build (commented out - large disk usage) # [doc("Build debug")] # build-debug: # @echo "=== Building debug ===" # cargo build --workspace --all-features # @echo "✓ Debug build complete" # Release build [doc("Build release")] build-release: @echo "=== Building release (optimized) ===" cargo build --workspace --all-features --release @echo "" @echo "Binaries:" @ls -lh target/release/typedialog* 2>/dev/null | awk '{printf " %-30s %8s\n", $NF, $5}' @echo "✓ Release build complete" # === COMPLIANCE STAGE === # Audit dependencies for security issues [doc("Audit dependencies for vulnerabilities")] audit: #!/bin/bash set -e echo "=== Auditing dependencies ===" cd "{{ WORKSPACE_ROOT }}" # Ensure HOME is set to absolute path, never literal ~ if [ -z "$HOME" ]; then export HOME="$(getent passwd $(whoami) | cut -d: -f6)" fi export CARGO_HOME="${HOME}/.cargo" # Use explicit --db path to prevent cargo audit from creating literal ~ directory # This is a workaround for a cargo audit issue with ~ expansion audit_db="${CARGO_HOME}/advisory-db" mkdir -p "$audit_db" if cargo audit --quiet --db "$audit_db" 2>&1 > /dev/null; then echo "✓ No vulnerabilities found" else echo "⚠️ Check failed (run 'cargo audit' manually)" fi # cargo-deny check (licenses only, show errors but suppress warnings) [doc("Check dependencies with cargo-deny")] deny: #!/bin/bash set -e echo "=== Running cargo-deny ===" cd "{{ WORKSPACE_ROOT }}" # Ensure HOME is set to absolute path if [ -z "$HOME" ]; then export HOME="$(getent passwd $(whoami) | cut -d: -f6)" fi { cargo deny check licenses 2>&1 | sed '/^warning\[/,/^$$/d' | grep -q "error\[" && cargo deny check licenses 2>&1 | sed '/^warning\[/,/^$$/d' && exit 1; } || echo "✓ No license violations" # Verify SBOMs can be generated [doc("Verify SBOMs can be generated")] verify-sbom: #!/bin/bash set -e echo "=== Verifying SBOMs ===" echo "Generating SBOMs to verify compliance..." cd "{{ WORKSPACE_ROOT }}" # Ensure HOME is set to absolute path if [ -z "$HOME" ]; then export HOME="$(getent passwd $(whoami) | cut -d: -f6)" fi cargo sbom --output-format spdx_json_2_3 > /tmp/sbom_spdx.json || echo "⚠️ SPDX SBOM generation failed" cargo sbom --output-format cyclone_dx_json_1_4 > /tmp/sbom_cyclonedx.json || echo "⚠️ CycloneDX SBOM generation failed" echo "✓ SBOM verification complete" # === FULL PIPELINE === # Complete CI pipeline [doc("Full CI: check + test + build")] full: #!/bin/bash set -e # Clean any stray ~ directory from previous runs (created by cargo audit download) if [ -d "~" ]; then rm -rf "~" echo "✓ Cleaned stray ~ directory" fi echo "╔═══════════════════════════════════════════════════════════╗" echo "║ TYPEDIALOG CI/CD PIPELINE ║" echo "╚═══════════════════════════════════════════════════════════╝" echo "" # Ensure HOME and CARGO_HOME are set to absolute paths (prevents cargo from creating literal ~ directory) if [ -z "$HOME" ]; then export HOME="$(getent passwd $(whoami) | cut -d: -f6)" fi # Force CARGO_HOME to absolute path to prevent literal ~ directory creation export CARGO_HOME="${HOME}/.cargo" # Pre-create CARGO_HOME if it doesn't exist mkdir -p "$CARGO_HOME" echo "ℹ️ Using HOME: $HOME" echo "ℹ️ Using CARGO_HOME: $CARGO_HOME" echo "" # Detect if target/debug exists at start debug_existed=0 if [ -d "target/debug" ]; then debug_existed=1 echo "ℹ️ target/debug detected - will preserve after CI" fi echo "" echo "=== Stage 1: Validation ===" just ci::fmt-check just ci::lint-all echo "" echo "=== Stage 2: Compliance ===" just ci::verify-sbom just ci::audit just ci::deny echo "" echo "=== Stage 3: Testing ===" just ci::test-all echo "" echo "=== Stage 4: Build Release ===" just ci::build-release echo "" echo "=== Cleanup ===" if [ $debug_existed -eq 0 ] && [ -d "/tmp/typedialog-ci" ]; then rm -rf /tmp/typedialog-ci echo "✓ Temporary artifacts cleaned" else if [ $debug_existed -eq 1 ]; then echo "✓ Preserving target/debug (already existed)" fi if [ -d "/tmp/typedialog-ci" ]; then echo "✓ Temporary CI directory left in place" fi fi echo "" echo "╔═══════════════════════════════════════════════════════════╗" echo "║ CI PIPELINE COMPLETE ✓ ║" echo "╚═══════════════════════════════════════════════════════════╝"