zorath-env 0.3.9

Fast CLI for .env validation against JSON/YAML schemas. 14 types, secret detection, watch mode, remote schemas, 7 export formats, CI templates, health diagnostics, code scanning, auto-fix. Language-agnostic single binary.
Documentation
//! CI/CD template generation for zenv validation workflows

use std::fs;

use crate::errors::CliError;

/// Available template names
pub const TEMPLATES: &[&str] = &["github", "gitlab", "circleci"];

/// Generate CI/CD template for the given platform
#[doc(hidden)]
pub fn run(template: &str, output: Option<&str>, list: bool, use_binary: bool) -> Result<(), CliError> {
    if list {
        println!("Available templates:");
        for name in TEMPLATES {
            println!("  {}", name);
        }
        return Ok(());
    }

    let content = match template.to_lowercase().as_str() {
        "github" | "gh" | "github-actions" => github_actions_template(use_binary),
        "gitlab" | "gl" | "gitlab-ci" => gitlab_ci_template(use_binary),
        "circleci" | "circle" => circleci_template(use_binary),
        _ => return Err(CliError::Input(format!(
            "Unknown template: '{}'. Available: {}",
            template,
            TEMPLATES.join(", ")
        ))),
    };

    match output {
        Some(path) => {
            fs::write(path, &content).map_err(|e| CliError::Input(format!("Failed to write {}: {}", path, e)))?;
            println!("Template written to {}", path);
        }
        None => {
            print!("{}", content);
        }
    }

    Ok(())
}

/// GitHub Actions workflow template
fn github_actions_template(use_binary: bool) -> String {
    let install_step = if use_binary {
        r#"      - name: Install zenv
        run: |
          curl -fsSL https://github.com/zorl-engine/zorath-env/releases/latest/download/zenv-linux-x64.tar.gz | tar xz
          sudo mv zenv /usr/local/bin/"#
    } else {
        r#"      - name: Install zenv
        run: cargo install zorath-env"#
    };

    format!(r#"# .github/workflows/env-validation.yml
# Generated by zenv template github{}
name: Validate Environment

on:
  push:
    paths:
      - '.env*'
      - 'env.schema.json'
      - 'env.schema.yaml'
  pull_request:
    paths:
      - '.env*'
      - 'env.schema.json'
      - 'env.schema.yaml'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

{}

      - name: Validate .env
        run: zenv check --allow-missing-env

      # Optional: Generate documentation
      # - name: Generate docs
      #   run: zenv docs > ENVIRONMENT.md

      # Optional: Check for secrets
      # - name: Detect secrets
      #   run: zenv check --detect-secrets
"#, if use_binary { " --use-binary" } else { "" }, install_step)
}

/// GitLab CI template
fn gitlab_ci_template(use_binary: bool) -> String {
    let install_script = if use_binary {
        r#"    - curl -fsSL https://github.com/zorl-engine/zorath-env/releases/latest/download/zenv-linux-x64.tar.gz | tar xz
    - mv zenv /usr/local/bin/"#
    } else {
        "    - cargo install zorath-env"
    };

    let image = if use_binary { "alpine:latest" } else { "rust:latest" };
    let extra_deps = if use_binary { "\n    - apk add --no-cache curl" } else { "" };

    format!(r#"# .gitlab-ci.yml
# Generated by zenv template gitlab{}
stages:
  - validate

validate-env:
  stage: validate
  image: {}
  before_script:{}
{}
  script:
    - zenv check --allow-missing-env
  rules:
    - changes:
        - .env*
        - env.schema.json
        - env.schema.yaml

# Optional: Generate documentation as artifact
# generate-docs:
#   stage: validate
#   image: {}
#   before_script:{}
#{}
#   script:
#     - zenv docs > ENVIRONMENT.md
#   artifacts:
#     paths:
#       - ENVIRONMENT.md
"#, if use_binary { " --use-binary" } else { "" }, image, extra_deps, install_script, image, extra_deps, install_script)
}

/// CircleCI config template
fn circleci_template(use_binary: bool) -> String {
    if use_binary {
        r#"# .circleci/config.yml
# Generated by zenv template circleci --use-binary
version: 2.1

executors:
  alpine:
    docker:
      - image: alpine:latest

jobs:
  validate-env:
    executor: alpine
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: apk add --no-cache curl
      - run:
          name: Install zenv
          command: |
            curl -fsSL https://github.com/zorl-engine/zorath-env/releases/latest/download/zenv-linux-x64.tar.gz | tar xz
            mv zenv /usr/local/bin/
      - run:
          name: Validate .env
          command: zenv check --allow-missing-env

workflows:
  version: 2
  validate:
    jobs:
      - validate-env:
          filters:
            branches:
              only:
                - main
                - develop
"#.to_string()
    } else {
        r#"# .circleci/config.yml
# Generated by zenv template circleci
version: 2.1

executors:
  rust:
    docker:
      - image: cimg/rust:1.75

jobs:
  validate-env:
    executor: rust
    steps:
      - checkout
      - run:
          name: Install zenv
          command: cargo install zorath-env
      - run:
          name: Validate .env
          command: zenv check --allow-missing-env

workflows:
  version: 2
  validate:
    jobs:
      - validate-env:
          filters:
            branches:
              only:
                - main
                - develop
"#.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_github_template_contains_workflow() {
        let template = github_actions_template(false);
        assert!(template.contains("name: Validate Environment"));
        assert!(template.contains("zenv check"));
        assert!(template.contains("cargo install zorath-env"));
    }

    #[test]
    fn test_github_template_binary_mode() {
        let template = github_actions_template(true);
        assert!(template.contains("name: Validate Environment"));
        assert!(template.contains("zenv check"));
        assert!(template.contains("curl -fsSL"));
        assert!(!template.contains("cargo install"));
    }

    #[test]
    fn test_gitlab_template_contains_stages() {
        let template = gitlab_ci_template(false);
        assert!(template.contains("stages:"));
        assert!(template.contains("validate-env:"));
        assert!(template.contains("cargo install zorath-env"));
    }

    #[test]
    fn test_gitlab_template_binary_mode() {
        let template = gitlab_ci_template(true);
        assert!(template.contains("stages:"));
        assert!(template.contains("validate-env:"));
        assert!(template.contains("curl -fsSL"));
        assert!(template.contains("alpine:latest"));
    }

    #[test]
    fn test_circleci_template_contains_jobs() {
        let template = circleci_template(false);
        assert!(template.contains("version: 2.1"));
        assert!(template.contains("validate-env:"));
        assert!(template.contains("cargo install zorath-env"));
    }

    #[test]
    fn test_circleci_template_binary_mode() {
        let template = circleci_template(true);
        assert!(template.contains("version: 2.1"));
        assert!(template.contains("validate-env:"));
        assert!(template.contains("curl -fsSL"));
        assert!(template.contains("alpine:latest"));
    }

    #[test]
    fn test_run_list_templates() {
        let result = run("", None, true, false);
        assert!(result.is_ok());
    }

    #[test]
    fn test_run_unknown_template() {
        let result = run("unknown", None, false, false);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown template"));
    }

    #[test]
    fn test_template_aliases() {
        // Test that aliases work
        let gh = run("gh", None, false, false);
        assert!(gh.is_ok());

        let gl = run("gl", None, false, false);
        assert!(gl.is_ok());

        let circle = run("circle", None, false, false);
        assert!(circle.is_ok());
    }
}